Initial 1.17 port.

This commit is contained in:
stfwi 2021-08-08 09:10:14 +02:00
parent 011edac67f
commit 3621b15989
242 changed files with 21371 additions and 22684 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,136 +1,131 @@
package wile.engineersdecor;
import wile.engineersdecor.blocks.*;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.OptionalRecipeCondition;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.container.ContainerType;
import net.minecraft.item.ItemGroup;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.*;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod("engineersdecor")
public class ModEngineersDecor
{
public static final String MODID = "engineersdecor";
public static final String MODNAME = "Engineer's Decor";
public static final int VERSION_DATAFIXER = 0;
private static final Logger LOGGER = LogManager.getLogger();
public ModEngineersDecor()
{
Auxiliaries.init(MODID, LOGGER, ModConfig::getServerConfig);
Auxiliaries.logGitVersion(MODNAME);
OptionalRecipeCondition.init(MODID, LOGGER);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigLoad);
FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigReload);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.SERVER, ModConfig.SERVER_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.COMMON, ModConfig.COMMON_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.CLIENT, ModConfig.CLIENT_CONFIG_SPEC);
MinecraftForge.EVENT_BUS.register(this);
}
public static final Logger logger() { return LOGGER; }
//
// Events
//
private void onSetup(final FMLCommonSetupEvent event)
{
LOGGER.info("Registering recipe condition processor ...");
CraftingHelper.register(OptionalRecipeCondition.Serializer.INSTANCE);
wile.engineersdecor.libmc.detail.Networking.init(MODID);
}
private void onClientSetup(final FMLClientSetupEvent event)
{
ModContent.registerContainerGuis(event);
ModContent.registerTileEntityRenderers(event);
ModContent.processContentClientSide(event);
wile.engineersdecor.libmc.detail.Overlay.register();
}
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public static class ForgeEvents
{
@SubscribeEvent
public static void onBlocksRegistry(final RegistryEvent.Register<Block> event)
{ ModContent.registerBlocks(event); }
@SubscribeEvent
public static void onItemRegistry(final RegistryEvent.Register<Item> event)
{ ModContent.registerItems(event); ModContent.registerBlockItems(event); }
@SubscribeEvent
public static void onTileEntityRegistry(final RegistryEvent.Register<TileEntityType<?>> event)
{ ModContent.registerTileEntities(event); }
@SubscribeEvent
public static void onRegisterEntityTypes(final RegistryEvent.Register<EntityType<?>> event)
{ ModContent.registerEntities(event); }
@SubscribeEvent
public static void onRegisterContainerTypes(final RegistryEvent.Register<ContainerType<?>> event)
{ ModContent.registerContainers(event); }
public static void onConfigLoad(net.minecraftforge.fml.config.ModConfig.Loading configEvent)
{ ModConfig.apply(); }
public static void onConfigReload(net.minecraftforge.fml.config.ModConfig.Reloading configEvent)
{
try {
ModEngineersDecor.logger().info("Config file changed {}", configEvent.getConfig().getFileName());
ModConfig.apply();
} catch(Throwable e) {
ModEngineersDecor.logger().error("Failed to load changed config: " + e.getMessage());
}
}
@SubscribeEvent
public static void onDataGeneration(GatherDataEvent event)
{
event.getGenerator().addProvider(new wile.engineersdecor.libmc.datagen.LootTableGen(event.getGenerator(), ModContent::allBlocks));
}
}
//
// Item group / creative tab
//
public static final ItemGroup ITEMGROUP = (new ItemGroup("tab" + MODID) {
@OnlyIn(Dist.CLIENT)
public ItemStack makeIcon()
{ return new ItemStack(ModContent.SIGN_MODLOGO); }
});
//
// Player update event
//
@SubscribeEvent
public void onPlayerEvent(final LivingEvent.LivingUpdateEvent event)
{
if((event.getEntity().level == null) || (!(event.getEntity() instanceof PlayerEntity))) return;
final PlayerEntity player = (PlayerEntity)event.getEntity();
if(player.onClimbable()) EdLadderBlock.onPlayerUpdateEvent(player);
}
}
package wile.engineersdecor;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import wile.engineersdecor.blocks.EdLadderBlock;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.OptionalRecipeCondition;
@Mod("engineersdecor")
public class ModEngineersDecor
{
public static final String MODID = "engineersdecor";
public static final String MODNAME = "Engineer's Decor";
public static final int VERSION_DATAFIXER = 0;
private static final Logger LOGGER = LogManager.getLogger();
public ModEngineersDecor()
{
Auxiliaries.init(MODID, LOGGER, ModConfig::getServerConfig);
Auxiliaries.logGitVersion(MODNAME);
OptionalRecipeCondition.init(MODID, LOGGER);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.SERVER, ModConfig.SERVER_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.COMMON, ModConfig.COMMON_CONFIG_SPEC);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
// FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigLoad);
// FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigReload);
MinecraftForge.EVENT_BUS.register(this);
}
public static Logger logger() { return LOGGER; }
//
// Events
//
private void onSetup(final FMLCommonSetupEvent event)
{
LOGGER.info("Registering recipe condition processor ...");
CraftingHelper.register(OptionalRecipeCondition.Serializer.INSTANCE);
wile.engineersdecor.libmc.detail.Networking.init(MODID);
}
private void onClientSetup(final FMLClientSetupEvent event)
{
ModContent.registerContainerGuis(event);
ModContent.registerTileEntityRenderers(event);
ModContent.processContentClientSide(event);
wile.engineersdecor.libmc.detail.Overlay.register();
}
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public static class ForgeEvents
{
@SubscribeEvent
public static void onBlocksRegistry(final RegistryEvent.Register<Block> event)
{ ModContent.registerBlocks(event); }
@SubscribeEvent
public static void onItemRegistry(final RegistryEvent.Register<Item> event)
{ ModContent.registerItems(event); ModContent.registerBlockItems(event); }
@SubscribeEvent
public static void onTileEntityRegistry(final RegistryEvent.Register<BlockEntityType<?>> event)
{ ModContent.registerTileEntities(event); }
@SubscribeEvent
public static void onRegisterEntityTypes(final RegistryEvent.Register<EntityType<?>> event)
{ ModContent.registerEntities(event); }
@SubscribeEvent
public static void onRegisterContainerTypes(final RegistryEvent.Register<MenuType<?>> event)
{ ModContent.registerContainers(event); }
/*
public static void onConfigLoad(net.minecraftforge.fml.config.ModConfig.Loading configEvent)
{ ModConfig.apply(); }
public static void onConfigReload(net.minecraftforge.fml.config.ModConfig.Reloading configEvent)
{
try {
ModEngineersDecor.logger().info("Config file changed {}", configEvent.getConfig().getFileName());
ModConfig.apply();
} catch(Throwable e) {
ModEngineersDecor.logger().error("Failed to load changed config: " + e.getMessage());
}
}
*/
}
//
// Item group / creative tab
//
public static final CreativeModeTab ITEMGROUP = (new CreativeModeTab("tab" + MODID) {
@OnlyIn(Dist.CLIENT)
public ItemStack makeIcon()
{ return new ItemStack(ModContent.SIGN_MODLOGO); }
});
//
// Player update event
//
@SubscribeEvent
public void onPlayerEvent(final LivingEvent.LivingUpdateEvent event)
{
if((event.getEntity().level == null) || (!(event.getEntity() instanceof final Player player))) return;
if(player.onClimbable()) EdLadderBlock.onPlayerUpdateEvent(player);
}
}

View file

@ -1,135 +1,34 @@
/*
* @file DecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for decor blocks.
* Mainly needed for:
* - MC block defaults.
* - Tooltip functionality
* - Model initialisation
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.shapes.VoxelShape;
import java.util.ArrayList;
import java.util.function.Supplier;
public class DecorBlock
{
public static final long CFG_DEFAULT = StandardBlocks.CFG_DEFAULT;
public static final long CFG_CUTOUT = StandardBlocks.CFG_CUTOUT;
public static final long CFG_MIPPED = StandardBlocks.CFG_MIPPED;
public static final long CFG_TRANSLUCENT = StandardBlocks.CFG_TRANSLUCENT;
public static final long CFG_WATERLOGGABLE = StandardBlocks.CFG_WATERLOGGABLE;
public static final long CFG_HORIZIONTAL = StandardBlocks.CFG_HORIZIONTAL;
public static final long CFG_LOOK_PLACEMENT = StandardBlocks.CFG_LOOK_PLACEMENT;
public static final long CFG_FACING_PLACEMENT = StandardBlocks.CFG_FACING_PLACEMENT;
public static final long CFG_OPPOSITE_PLACEMENT = StandardBlocks.CFG_OPPOSITE_PLACEMENT;
public static final long CFG_FLIP_PLACEMENT_IF_SAME = StandardBlocks.CFG_FLIP_PLACEMENT_IF_SAME;
public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = StandardBlocks.CFG_FLIP_PLACEMENT_SHIFTCLICK;
public static final long CFG_STRICT_CONNECTIONS = StandardBlocks.CFG_STRICT_CONNECTIONS;
public static final long CFG_AI_PASSABLE = StandardBlocks.CFG_AI_PASSABLE;
public static final long CFG_HARD_IE_DEPENDENT = 0x8000000000000000L;
@Deprecated public static final long CFG_EXPERIMENTAL = 0x4000000000000000L;
public static class Normal extends StandardBlocks.BaseBlock implements IDecorBlock
{
public Normal(long conf, AbstractBlock.Properties properties)
{ super(conf, properties); }
}
public static class Cutout extends StandardBlocks.Cutout implements IDecorBlock
{
public Cutout(long conf, AbstractBlock.Properties properties)
{ super(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16,16 )); }
public Cutout(long conf, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(conf, properties, aabb);}
public Cutout(long conf, AbstractBlock.Properties properties, VoxelShape voxel_shape)
{ super(conf, properties, voxel_shape); }
public Cutout(long conf, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(conf, properties, aabbs); }
}
public static class WaterLoggable extends StandardBlocks.WaterLoggable implements IStandardBlock, IWaterLoggable
{
public WaterLoggable(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
public WaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public WaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public WaterLoggable(long config, AbstractBlock.Properties properties, VoxelShape voxel_shape)
{ super(config, properties, voxel_shape); }
}
public static class Directed extends StandardBlocks.Directed implements IDecorBlock
{
public Directed(long config, AbstractBlock.Properties properties, final AxisAlignedBB unrotatedAABB)
{ super(config, properties, unrotatedAABB); }
public Directed(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, properties, unrotatedAABBs); }
public Directed(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class DirectedWaterLoggable extends StandardBlocks.DirectedWaterLoggable implements IDecorBlock,IWaterLoggable
{
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class Horizontal extends StandardBlocks.Horizontal implements IDecorBlock
{
public Horizontal(long config, AbstractBlock.Properties properties, final AxisAlignedBB unrotatedAABB)
{ super(config, properties, unrotatedAABB); }
public Horizontal(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, properties, unrotatedAABBs); }
public Horizontal(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class HorizontalWaterLoggable extends StandardBlocks.HorizontalWaterLoggable implements IWaterLoggable
{
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class HorizontalFourWayWaterLoggable extends StandardBlocks.HorizontalFourWayWaterLoggable implements IWaterLoggable
{
public HorizontalFourWayWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB base_aabb, AxisAlignedBB side_aabb, int railing_height_extension)
{ super(config, properties, base_aabb, side_aabb, railing_height_extension); }
}
}
/*
* @file DecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for decor blocks.
* Mainly needed for:
* - MC block defaults.
* - Tooltip functionality
* - Model initialisation
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
public class DecorBlock
{
public static final long CFG_DEFAULT = StandardBlocks.CFG_DEFAULT;
public static final long CFG_CUTOUT = StandardBlocks.CFG_CUTOUT;
public static final long CFG_MIPPED = StandardBlocks.CFG_MIPPED;
public static final long CFG_TRANSLUCENT = StandardBlocks.CFG_TRANSLUCENT;
public static final long CFG_WATERLOGGABLE = StandardBlocks.CFG_WATERLOGGABLE;
public static final long CFG_HORIZIONTAL = StandardBlocks.CFG_HORIZIONTAL;
public static final long CFG_LOOK_PLACEMENT = StandardBlocks.CFG_LOOK_PLACEMENT;
public static final long CFG_FACING_PLACEMENT = StandardBlocks.CFG_FACING_PLACEMENT;
public static final long CFG_OPPOSITE_PLACEMENT = StandardBlocks.CFG_OPPOSITE_PLACEMENT;
public static final long CFG_FLIP_PLACEMENT_IF_SAME = StandardBlocks.CFG_FLIP_PLACEMENT_IF_SAME;
public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = StandardBlocks.CFG_FLIP_PLACEMENT_SHIFTCLICK;
public static final long CFG_STRICT_CONNECTIONS = StandardBlocks.CFG_STRICT_CONNECTIONS;
public static final long CFG_AI_PASSABLE = StandardBlocks.CFG_AI_PASSABLE;
public static final long CFG_HARD_IE_DEPENDENT = 0x8000000000000000L;
@Deprecated public static final long CFG_EXPERIMENTAL = 0x4000000000000000L;
}

View file

@ -1,356 +1,349 @@
/*
* @file EdBreaker.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Block Breaker
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import wile.engineersdecor.libmc.detail.Overlay;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.GameRules;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Blocks;
import net.minecraft.block.SoundType;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class EdBreaker
{
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{ BreakerTileEntity.on_config(boost_energy_per_tick, breaking_time_per_hardness, min_breaking_time_ticks, power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public BreakerBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, builder, unrotatedAABBs); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new BreakerTileEntity(); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
final double rv = rnd.nextDouble();
if(rv > 0.8) return;
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST: world.addParticle(ParticleTypes.SMOKE, x-xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case EAST: world.addParticle(ParticleTypes.SMOKE, x+xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case NORTH: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z-xc, 0.0, 0.0, 0.0); break;
default: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z+xc, 0.0, 0.0, 0.0); break;
}
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
if(!(world instanceof World) || (((World) world).isClientSide)) return;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof BreakerTileEntity)) return;
((BreakerTileEntity)te).block_updated();
}
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof BreakerTileEntity) ((BreakerTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerTileEntity extends TileEntity implements ITickableTileEntity
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 8;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_BREAKING_RELUCTANCE = 17;
public static final int DEFAULT_MIN_BREAKING_TIME = 15;
public static final int MAX_BREAKING_TIME = 800;
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = 32000;
private static int breaking_reluctance = DEFAULT_BREAKING_RELUCTANCE;
private static int min_breaking_time = DEFAULT_MIN_BREAKING_TIME;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int time_needed_;
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * MathHelper.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 100000);
breaking_reluctance = MathHelper.clamp(breaking_time_per_hardness, 5, 50);
min_breaking_time = MathHelper.clamp(min_breaking_time_ticks, 10, 100);
requires_power = power_required;
ModConfig.log("Config block breaker: Boost energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t, reluctance=" + breaking_reluctance + "t/hrdn, break time offset=" + min_breaking_time + "t.");
}
public BreakerTileEntity()
{ this(ModContent.TET_SMALL_BLOCK_BREAKER); }
public BreakerTileEntity(TileEntityType<?> te_type)
{
super(te_type);
battery_ = new RfEnergy.Battery(energy_max, boost_energy_consumption, 0);
energy_handler_ = battery_.createEnergyHandler();
}
public void block_updated()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
public void readnbt(CompoundNBT nbt)
{ battery_.load(nbt); }
private void writenbt(CompoundNBT nbt)
{ battery_.save(nbt); }
public void state_message(PlayerEntity player)
{
String progress = "0";
if((proc_time_elapsed_ > 0) && (time_needed_ > 0)) {
progress = Integer.toString((int)MathHelper.clamp((((double)proc_time_elapsed_) / ((double)time_needed_) * 100), 0, 100));
}
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_block_breaker.status", new Object[]{battery_.getSOC(), energy_max, progress }));
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
private static final HashSet<Block> blacklist = new HashSet<>();
static {
blacklist.add(Blocks.AIR);
blacklist.add(Blocks.BEDROCK);
blacklist.add(Blocks.FIRE);
blacklist.add(Blocks.END_PORTAL);
blacklist.add(Blocks.END_GATEWAY);
blacklist.add(Blocks.END_PORTAL_FRAME);
blacklist.add(Blocks.NETHER_PORTAL);
blacklist.add(Blocks.BARRIER);
}
private static boolean isBreakable(BlockState state, BlockPos pos, World world)
{
final Block block = state.getBlock();
if(blacklist.contains(block)) return false;
if(state.getMaterial().isLiquid()) return false;
if(block.isAir(state, world, pos)) return false;
float bh = state.getDestroySpeed(world, pos);
if((bh<0) || (bh>55)) return false;
return true;
}
private static void spawnBlockAsEntity(World world, BlockPos pos, ItemStack stack) {
if(world.isClientSide || stack.isEmpty() || (!world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) || world.restoringBlockSnapshots) return;
ItemEntity e = new ItemEntity(world,
((world.random.nextFloat()*0.1)+0.5) + pos.getX(),
((world.random.nextFloat()*0.1)+0.5) + pos.getY(),
((world.random.nextFloat()*0.1)+0.5) + pos.getZ(),
stack
);
e.setDefaultPickUpDelay();
e.setDeltaMovement((world.random.nextFloat()*0.1-0.05), (world.random.nextFloat()*0.1-0.03), (world.random.nextFloat()*0.1-0.05));
world.addFreshEntity(e);
}
private static boolean canInsertInto(World world, BlockPos pos)
{
// Maybe make a tag for that. The question is if it is actually worth it, or if that would be only
// tag spamming the game. So for now only FH and VH.
final BlockState state = world.getBlockState(pos);
return (state.getBlock() == ModContent.FACTORY_HOPPER) || (state.getBlock() == Blocks.HOPPER);
}
private boolean breakBlock(BlockState state, BlockPos pos, World world)
{
if(world.isClientSide || (!(world instanceof ServerWorld)) || world.restoringBlockSnapshots) return false; // retry next cycle
List<ItemStack> drops;
final Block block = state.getBlock();
final boolean insert = canInsertInto(world, getBlockPos().below());
drops = Block.getDrops(state, (ServerWorld)world, pos, world.getBlockEntity(pos));
world.removeBlock(pos, false);
for(ItemStack drop:drops) {
if(!insert) {
spawnBlockAsEntity(world, pos, drop);
} else {
final ItemStack remaining = Inventories.insert(world, getBlockPos().below(), Direction.UP, drop, false);
if(!remaining.isEmpty()) spawnBlockAsEntity(world, pos, remaining);
}
}
SoundType stype = state.getBlock().getSoundType(state, world, pos, null);
if(stype != null) world.playSound(null, pos, stype.getPlaceSound(), SoundCategory.BLOCKS, stype.getVolume()*0.6f, stype.getPitch());
return true;
}
@Override
@SuppressWarnings("deprecation")
public void tick()
{
if(--tick_timer_ > 0) return;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof BreakerBlock)) return;
if(level.isClientSide) {
if(!device_state.getValue(BreakerBlock.ACTIVE)) {
tick_timer_ = TICK_INTERVAL;
} else {
tick_timer_ = 1;
// not sure if is so cool to do this each tick ... may be simplified/removed again.
SoundEvent sound = SoundEvents.WOOD_HIT;
BlockState target_state = level.getBlockState(worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING)));
SoundType stype = target_state.getBlock().getSoundType(target_state);
if((stype == SoundType.WOOL) || (stype == SoundType.GRASS) || (stype == SoundType.SNOW)) {
sound = SoundEvents.WOOL_HIT;
} else if((stype == SoundType.GRAVEL) || (stype == SoundType.SAND)) {
sound = SoundEvents.GRAVEL_HIT;
}
level.playLocalSound(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), sound, SoundCategory.BLOCKS, 0.1f, 1.2f, false);
}
} else {
tick_timer_ = TICK_INTERVAL;
final BlockPos target_pos = worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING));
final BlockState target_state = level.getBlockState(target_pos);
if((level.hasNeighborSignal(worldPosition)) || (!isBreakable(target_state, target_pos, level))) {
if(device_state.getValue(BreakerBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
time_needed_ = MathHelper.clamp((int)(target_state.getDestroySpeed(level, worldPosition) * breaking_reluctance) + min_breaking_time, min_breaking_time, MAX_BREAKING_TIME);
if(battery_.draw(boost_energy_consumption)) {
proc_time_elapsed_ += TICK_INTERVAL * (1+BOOST_FACTOR);
time_needed_ += min_breaking_time * (3*BOOST_FACTOR/5);
active_timer_ = 2;
} else if(!requires_power) {
proc_time_elapsed_ += TICK_INTERVAL;
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= time_needed_) {
proc_time_elapsed_ = 0;
breakBlock(target_state, target_pos, level);
active = false;
}
if(device_state.getValue(BreakerBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, active), 1|2);
}
}
}
}
}
/*
* @file EdBreaker.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Block Breaker
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import wile.engineersdecor.libmc.detail.Overlay;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class EdBreaker
{
public static final int BOOST_FACTOR = 8;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_BREAKING_RELUCTANCE = 17;
public static final int DEFAULT_MIN_BREAKING_TIME = 15;
public static final int MAX_BREAKING_TIME = 800;
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = 32000;
private static int breaking_reluctance = DEFAULT_BREAKING_RELUCTANCE;
private static int min_breaking_time = DEFAULT_MIN_BREAKING_TIME;
private static boolean requires_power = false;
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{
final int interval = BreakerTileEntity.TICK_INTERVAL;
boost_energy_consumption = interval * Mth.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 100000);
breaking_reluctance = Mth.clamp(breaking_time_per_hardness, 5, 50);
min_breaking_time = Mth.clamp(min_breaking_time_ticks, 10, 100);
requires_power = power_required;
ModConfig.log("Config block breaker: Boost energy consumption:" + (boost_energy_consumption/interval) + "rf/t, reluctance=" + breaking_reluctance + "t/hrdn, break time offset=" + min_breaking_time + "t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerBlock extends StandardBlocks.HorizontalWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<BreakerTileEntity>
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public BreakerBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBs)
{ super(config, builder, unrotatedAABBs); }
@Nullable
@Override
public BlockEntityType<BreakerTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_BLOCK_BREAKER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
// Sound
if(true || (world.getGameTime() & 0x1) == 0) {
SoundEvent sound = SoundEvents.WOOD_HIT;
BlockState target_state = world.getBlockState(pos.relative(state.getValue(BreakerBlock.HORIZONTAL_FACING)));
SoundType stype = target_state.getBlock().getSoundType(target_state);
if((stype == SoundType.WOOL) || (stype == SoundType.GRASS) || (stype == SoundType.SNOW)) {
sound = SoundEvents.WOOL_HIT;
} else if((stype == SoundType.GRAVEL) || (stype == SoundType.SAND)) {
sound = SoundEvents.GRAVEL_HIT;
}
world.playLocalSound(pos.getX(), pos.getY(), pos.getZ(), sound, SoundSource.BLOCKS, 0.1f, 1.2f, false);
}
// Particles
{
final double rv = rnd.nextDouble();
if(rv < 0.8) {
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch (state.getValue(HORIZONTAL_FACING)) {
case WEST -> world.addParticle(ParticleTypes.SMOKE, x - xc, yr, z + xr, 0.0, 0.0, 0.0);
case EAST -> world.addParticle(ParticleTypes.SMOKE, x + xc, yr, z + xr, 0.0, 0.0, 0.0);
case NORTH -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z - xc, 0.0, 0.0, 0.0);
default -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z + xc, 0.0, 0.0, 0.0);
}
}
}
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
if(!(world instanceof Level) || (world.isClientSide)) return;
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof BreakerTileEntity)) return;
((BreakerTileEntity)te).block_updated();
}
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof BreakerTileEntity) ((BreakerTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int time_needed_;
private final RfEnergy.Battery battery_ = new RfEnergy.Battery(energy_max, boost_energy_consumption, 0);
private final LazyOptional<IEnergyStorage> energy_handler_ = battery_.createEnergyHandler();
public BreakerTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_BLOCK_BREAKER, pos, state); }
public void block_updated()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
public void readnbt(CompoundTag nbt)
{ battery_.load(nbt); }
private void writenbt(CompoundTag nbt)
{ battery_.save(nbt); }
public void state_message(Player player)
{
String progress = "0";
if((proc_time_elapsed_ > 0) && (time_needed_ > 0)) {
progress = Integer.toString((int)Mth.clamp((((double)proc_time_elapsed_) / ((double)time_needed_) * 100), 0, 100));
}
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_block_breaker.status", battery_.getSOC(), energy_max, progress));
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
private static final HashSet<Block> blacklist = new HashSet<>();
static {
blacklist.add(Blocks.AIR);
blacklist.add(Blocks.BEDROCK);
blacklist.add(Blocks.FIRE);
blacklist.add(Blocks.END_PORTAL);
blacklist.add(Blocks.END_GATEWAY);
blacklist.add(Blocks.END_PORTAL_FRAME);
blacklist.add(Blocks.NETHER_PORTAL);
blacklist.add(Blocks.BARRIER);
}
private static boolean isBreakable(BlockState state, BlockPos pos, Level world)
{
final Block block = state.getBlock();
if(blacklist.contains(block)) return false;
if(state.isAir()) return false;
if(state.getMaterial().isLiquid()) return false;
float bh = state.getDestroySpeed(world, pos);
return !((bh<0) || (bh>55));
}
private static void spawnBlockAsEntity(Level world, BlockPos pos, ItemStack stack) {
if(world.isClientSide || stack.isEmpty() || (!world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) || world.restoringBlockSnapshots) return;
ItemEntity e = new ItemEntity(world,
((world.random.nextFloat()*0.1)+0.5) + pos.getX(),
((world.random.nextFloat()*0.1)+0.5) + pos.getY(),
((world.random.nextFloat()*0.1)+0.5) + pos.getZ(),
stack
);
e.setDefaultPickUpDelay();
e.setDeltaMovement((world.random.nextFloat()*0.1-0.05), (world.random.nextFloat()*0.1-0.03), (world.random.nextFloat()*0.1-0.05));
world.addFreshEntity(e);
}
private static boolean canInsertInto(Level world, BlockPos pos)
{
// Maybe make a tag for that. The question is if it is actually worth it, or if that would be only
// tag spamming the game. So for now only FH and VH.
final BlockState state = world.getBlockState(pos);
return (state.getBlock() == ModContent.FACTORY_HOPPER) || (state.getBlock() == Blocks.HOPPER);
}
private boolean breakBlock(BlockState state, BlockPos pos, Level world)
{
if(world.isClientSide || (!(world instanceof ServerLevel)) || world.restoringBlockSnapshots) return false; // retry next cycle
List<ItemStack> drops;
final Block block = state.getBlock();
final boolean insert = canInsertInto(world, getBlockPos().below());
drops = Block.getDrops(state, (ServerLevel)world, pos, world.getBlockEntity(pos));
world.removeBlock(pos, false);
for(ItemStack drop:drops) {
if(!insert) {
spawnBlockAsEntity(world, pos, drop);
} else {
final ItemStack remaining = Inventories.insert(world, getBlockPos().below(), Direction.UP, drop, false);
if(!remaining.isEmpty()) spawnBlockAsEntity(world, pos, remaining);
}
}
SoundType stype = state.getBlock().getSoundType(state, world, pos, null);
if(stype != null) world.playSound(null, pos, stype.getPlaceSound(), SoundSource.BLOCKS, stype.getVolume()*0.6f, stype.getPitch());
return true;
}
@Override
@SuppressWarnings("deprecation")
public void tick()
{
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof BreakerBlock)) return;
final BlockPos target_pos = worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING));
final BlockState target_state = level.getBlockState(target_pos);
if((level.hasNeighborSignal(worldPosition)) || (!isBreakable(target_state, target_pos, level))) {
if(device_state.getValue(BreakerBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
time_needed_ = Mth.clamp((int)(target_state.getDestroySpeed(level, worldPosition) * breaking_reluctance) + min_breaking_time, min_breaking_time, MAX_BREAKING_TIME);
if(battery_.draw(boost_energy_consumption)) {
proc_time_elapsed_ += TICK_INTERVAL * (1+BOOST_FACTOR);
time_needed_ += min_breaking_time * (3*BOOST_FACTOR/5);
active_timer_ = 2;
} else if(!requires_power) {
proc_time_elapsed_ += TICK_INTERVAL;
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= time_needed_) {
proc_time_elapsed_ = 0;
breakBlock(target_state, target_pos, level);
active = false;
}
if(device_state.getValue(BreakerBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, active), 1|2);
}
}
}
}

View file

@ -1,131 +1,138 @@
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkBlock extends DecorBlock.HorizontalFourWayWaterLoggable implements IDecorBlock
{
final Block railing_block;
final AxisAlignedBB base_aabb;
public EdCatwalkBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB base_aabb, final AxisAlignedBB railing_aabb, final Block railing_block)
{ super(config, properties, base_aabb, railing_aabb, 0); this.railing_block = railing_block; this.base_aabb=base_aabb; }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); }
public static boolean place_consume(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, int shrink)
{
if(!world.setBlock(pos, state, 1|2)) return false;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if((!player.isCreative()) && (!world.isClientSide())) {
ItemStack stack = player.getItemInHand(hand);
if(shrink >= 0) {
stack.shrink(shrink);
} else if(stack.getCount() < stack.getMaxStackSize()) {
stack.grow(Math.abs(shrink));
} else {
Inventories.give(player, new ItemStack(stack.getItem(), Math.abs(shrink)));
}
Inventories.setItemInPlayerHand(player, hand, stack);
}
return true;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return ActionResultType.PASS;
final Block block = ((BlockItem)item).getBlock();
if(block == this) {
if(hit.getDirection().getAxis().isHorizontal()) return ActionResultType.PASS; // place new block on the clicked side.
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
place_consume(place_state, world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
if(block == railing_block) {
Direction face = hit.getDirection();
final Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos()));
if(face.getAxis().isHorizontal()) {
// Side or railing clicked
if(rhv.multiply(Vector3d.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
} else if(player.distanceToSqr(Vector3d.atCenterOf(pos)) < 3) {
// near accurate placement
face = Direction.getNearest(rhv.x, 0, rhv.z);
} else {
// far automatic placement
face = Direction.getNearest(player.getLookAngle().x, 0, player.getLookAngle().z);
List<Direction> free_sides = Arrays.stream(Direction.values()).filter(d->d.getAxis().isHorizontal() && (world.getBlockState(pos.relative(d)).getBlock()!=this)).collect(Collectors.toList());
if(free_sides.isEmpty()) return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
if(!free_sides.contains(face)) face = free_sides.get(0);
}
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
place_consume(state.setValue(railing, add), world, pos, player, hand, add ? 1 : -1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
return ActionResultType.PASS;
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkBlock extends StandardBlocks.HorizontalFourWayWaterLoggable
{
final Block railing_block;
final AABB base_aabb;
public EdCatwalkBlock(long config, BlockBehaviour.Properties properties, final AABB base_aabb, final AABB railing_aabb, final Block railing_block)
{ super(config, properties, base_aabb, railing_aabb, 0); this.railing_block = railing_block; this.base_aabb=base_aabb; }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); }
public static boolean place_consume(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, int shrink)
{
if(!world.setBlock(pos, state, 1|2)) return false;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if((!player.isCreative()) && (!world.isClientSide())) {
ItemStack stack = player.getItemInHand(hand);
if(shrink >= 0) {
stack.shrink(shrink);
} else if(stack.getCount() < stack.getMaxStackSize()) {
stack.grow(Math.abs(shrink));
} else {
Inventories.give(player, new ItemStack(stack.getItem(), Math.abs(shrink)));
}
Inventories.setItemInPlayerHand(player, hand, stack);
}
return true;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return InteractionResult.PASS;
final Block block = ((BlockItem)item).getBlock();
if(block == this) {
if(hit.getDirection().getAxis().isHorizontal()) return InteractionResult.PASS; // place new block on the clicked side.
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
place_consume(place_state, world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
if(block == railing_block) {
Direction face = hit.getDirection();
final Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos()));
if(face.getAxis().isHorizontal()) {
// Side or railing clicked
if(rhv.multiply(Vec3.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
} else if(player.distanceToSqr(Vec3.atCenterOf(pos)) < 3) {
// near accurate placement
face = Direction.getNearest(rhv.x, 0, rhv.z);
} else {
// far automatic placement
face = Direction.getNearest(player.getLookAngle().x, 0, player.getLookAngle().z);
List<Direction> free_sides = Arrays.stream(Direction.values()).filter(d->d.getAxis().isHorizontal() && (world.getBlockState(pos.relative(d)).getBlock()!=this)).collect(Collectors.toList());
if(free_sides.isEmpty()) return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
if(!free_sides.contains(face)) face = free_sides.get(0);
}
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
place_consume(state.setValue(railing, add), world, pos, player, hand, add ? 1 : -1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
return InteractionResult.PASS;
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}

View file

@ -1,183 +1,186 @@
/*
* @file EdCatwalkStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stair version of the catwalk block, optional left/right railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCatwalkStairsBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty RIGHT_RAILING = BooleanProperty.create("right_railing");
public static final BooleanProperty LEFT_RAILING = BooleanProperty.create("left_railing");
protected final Map<BlockState, VoxelShape> shapes;
protected final Map<BlockState, VoxelShape> collision_shapes;
protected final Map<Direction, Integer> y_rotations;
public EdCatwalkStairsBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] base_aabb, final AxisAlignedBB[] railing_aabbs)
{
super(config, properties, base_aabb);
Map<BlockState, VoxelShape> sh = new HashMap<>();
Map<BlockState, VoxelShape> csh = new HashMap<>();
getStateDefinition().getPossibleStates().forEach(state->{
Direction facing = state.getValue(HORIZONTAL_FACING);
VoxelShape base_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(base_aabb, facing, true));
if(state.getValue(RIGHT_RAILING)) {
VoxelShape right_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(Auxiliaries.getMirroredAABB(railing_aabbs, Axis.X), facing, true));
base_shape = VoxelShapes.joinUnoptimized(base_shape, right_shape, IBooleanFunction.OR);
}
if(state.getValue(LEFT_RAILING)) {
VoxelShape left_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(railing_aabbs, facing, true));
base_shape = VoxelShapes.joinUnoptimized(base_shape, left_shape, IBooleanFunction.OR);
}
sh.put(state, base_shape);
csh.put(state, base_shape);
});
shapes = sh;
collision_shapes = csh;
y_rotations = new HashMap<>();
y_rotations.put(Direction.NORTH, 0);
y_rotations.put(Direction.EAST, 1);
y_rotations.put(Direction.SOUTH, 2);
y_rotations.put(Direction.WEST, 3);
y_rotations.put(Direction.UP, 0);
y_rotations.put(Direction.DOWN, 0);
registerDefaultState(super.defaultBlockState().setValue(LEFT_RAILING, false).setValue(RIGHT_RAILING, false));
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context)
{ return shapes.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context)
{ return collision_shapes.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RIGHT_RAILING, LEFT_RAILING); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return ActionResultType.PASS;
final Block block = ((BlockItem)item).getBlock();
final Direction facing = state.getValue(HORIZONTAL_FACING);
if(block == this) {
final Direction hlv = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis().isHorizontal()).findFirst().orElse(Direction.NORTH);
BlockPos adjacent_pos;
if(hlv == facing) {
adjacent_pos = pos.above().relative(hlv);
} else if(hlv == facing.getOpposite()) {
adjacent_pos = pos.below().relative(hlv);
} else {
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return ActionResultType.CONSUME;
BlockState place_state = defaultBlockState().setValue(HORIZONTAL_FACING, facing);
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
} else if((block == ModContent.STEEL_CATWALK) || (block == ModContent.STEEL_CATWALK_TOP_ALIGNED)) {
BlockPos adjacent_pos;
adjacent_pos = pos.relative(facing);
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return ActionResultType.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return ActionResultType.CONSUME;
BlockState place_state = ModContent.STEEL_CATWALK_TOP_ALIGNED.defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
} else if(block == ModContent.STEEL_RAILING) {
Direction face = hit.getDirection();
int shrink = 0;
BlockState place_state = state;
if(face == Direction.UP) {
Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos())).multiply(new Vector3d(1,0,1)).cross(Vector3d.atLowerCornerOf(facing.getNormal()));
face = (rhv.y > 0) ? (facing.getClockWise()) : (facing.getCounterClockWise());
}
if(face == facing.getClockWise()) {
if(state.getValue(RIGHT_RAILING)) {
place_state = state.setValue(RIGHT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(RIGHT_RAILING, true);
shrink = 1;
}
} else if(face == facing.getCounterClockWise()) {
if(state.getValue(LEFT_RAILING)) {
place_state = state.setValue(LEFT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(LEFT_RAILING, true);
shrink = 1;
}
}
if(shrink != 0) EdCatwalkBlock.place_consume(place_state, world, pos, player, hand, shrink);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
return ActionResultType.PASS;
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(LEFT_RAILING)?1:0)+(state.getValue(RIGHT_RAILING)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}
/*
* @file EdCatwalkStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stair version of the catwalk block, optional left/right railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCatwalkStairsBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final BooleanProperty RIGHT_RAILING = BooleanProperty.create("right_railing");
public static final BooleanProperty LEFT_RAILING = BooleanProperty.create("left_railing");
protected final Map<BlockState, VoxelShape> shapes;
protected final Map<BlockState, VoxelShape> collision_shapes;
protected final Map<Direction, Integer> y_rotations;
public EdCatwalkStairsBlock(long config, BlockBehaviour.Properties properties, final AABB[] base_aabb, final AABB[] railing_aabbs)
{
super(config, properties, base_aabb);
Map<BlockState, VoxelShape> sh = new HashMap<>();
Map<BlockState, VoxelShape> csh = new HashMap<>();
getStateDefinition().getPossibleStates().forEach(state->{
Direction facing = state.getValue(HORIZONTAL_FACING);
VoxelShape base_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(base_aabb, facing, true));
if(state.getValue(RIGHT_RAILING)) {
VoxelShape right_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(Auxiliaries.getMirroredAABB(railing_aabbs, Direction.Axis.X), facing, true));
base_shape = Shapes.joinUnoptimized(base_shape, right_shape, BooleanOp.OR);
}
if(state.getValue(LEFT_RAILING)) {
VoxelShape left_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(railing_aabbs, facing, true));
base_shape = Shapes.joinUnoptimized(base_shape, left_shape, BooleanOp.OR);
}
sh.put(state, base_shape);
csh.put(state, base_shape);
});
shapes = sh;
collision_shapes = csh;
y_rotations = new HashMap<>();
y_rotations.put(Direction.NORTH, 0);
y_rotations.put(Direction.EAST, 1);
y_rotations.put(Direction.SOUTH, 2);
y_rotations.put(Direction.WEST, 3);
y_rotations.put(Direction.UP, 0);
y_rotations.put(Direction.DOWN, 0);
registerDefaultState(super.defaultBlockState().setValue(LEFT_RAILING, false).setValue(RIGHT_RAILING, false));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context)
{ return shapes.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context)
{ return collision_shapes.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RIGHT_RAILING, LEFT_RAILING); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return InteractionResult.PASS;
final Block block = ((BlockItem)item).getBlock();
final Direction facing = state.getValue(HORIZONTAL_FACING);
if(block == this) {
final Direction hlv = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis().isHorizontal()).findFirst().orElse(Direction.NORTH);
BlockPos adjacent_pos;
if(hlv == facing) {
adjacent_pos = pos.above().relative(hlv);
} else if(hlv == facing.getOpposite()) {
adjacent_pos = pos.below().relative(hlv);
} else {
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return InteractionResult.CONSUME;
BlockState place_state = defaultBlockState().setValue(HORIZONTAL_FACING, facing);
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
} else if((block == ModContent.STEEL_CATWALK) || (block == ModContent.STEEL_CATWALK_TOP_ALIGNED)) {
BlockPos adjacent_pos;
adjacent_pos = pos.relative(facing);
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return InteractionResult.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return InteractionResult.CONSUME;
BlockState place_state = ModContent.STEEL_CATWALK_TOP_ALIGNED.defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
} else if(block == ModContent.STEEL_RAILING) {
Direction face = hit.getDirection();
int shrink = 0;
BlockState place_state = state;
if(face == Direction.UP) {
Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos())).multiply(new Vec3(1,0,1)).cross(Vec3.atLowerCornerOf(facing.getNormal()));
face = (rhv.y > 0) ? (facing.getClockWise()) : (facing.getCounterClockWise());
}
if(face == facing.getClockWise()) {
if(state.getValue(RIGHT_RAILING)) {
place_state = state.setValue(RIGHT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(RIGHT_RAILING, true);
shrink = 1;
}
} else if(face == facing.getCounterClockWise()) {
if(state.getValue(LEFT_RAILING)) {
place_state = state.setValue(LEFT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(LEFT_RAILING, true);
shrink = 1;
}
}
if(shrink != 0) EdCatwalkBlock.place_consume(place_state, world, pos, player, hand, shrink);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
return InteractionResult.PASS;
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(LEFT_RAILING)?1:0)+(state.getValue(RIGHT_RAILING)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}

View file

@ -1,106 +1,111 @@
/*
* @file EdCatwalkTopAlignedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Top aligned platforms, down-connection to poles.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
public class EdCatwalkTopAlignedBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public static final IntegerProperty VARIANT = IntegerProperty.create("variant", 0, 3);
protected final List<VoxelShape> variant_shapes;
public EdCatwalkTopAlignedBlock(long config, AbstractBlock.Properties properties, final VoxelShape[] variant_shapes)
{
super(config, properties, variant_shapes[0]);
registerDefaultState(super.defaultBlockState().setValue(VARIANT, 0));
this.variant_shapes = VARIANT.getPossibleValues().stream().map(i->(i<variant_shapes.length) ? (variant_shapes[i]) : (VoxelShapes.block())).collect(Collectors.toList());
}
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return variant_shapes.get(state.getValue(VARIANT)); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = adapted_state(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos());
if(context.getClickedFace() != Direction.UP) return state;
BlockState below = context.getLevel().getBlockState(context.getClickedPos().below());
if((state.getValue(VARIANT)==0) && (below.isFaceSturdy(context.getLevel(), context.getClickedPos().below(), Direction.UP))) return state.setValue(VARIANT, 3);
return state;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if(item != this.asItem()) return ActionResultType.PASS;
if(hit.getDirection().getAxis().isHorizontal()) return ActionResultType.PASS;
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(adapted_state(place_state, world, adjacent_pos), world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return adapted_state(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
// ---
private BlockState adapted_state(BlockState state, IWorld world, BlockPos pos)
{
BlockState below = world.getBlockState(pos.below());
if((below == null) || (state == null)) return state;
if((below.getBlock() == ModContent.THICK_STEEL_POLE) || (below.getBlock() == ModContent.THICK_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 1);
if((below.getBlock() == ModContent.THIN_STEEL_POLE) || (below.getBlock() == ModContent.THIN_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 2);
return state;
}
}
/*
* @file EdCatwalkTopAlignedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Top aligned platforms, down-connection to poles.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkTopAlignedBlock extends StandardBlocks.WaterLoggable
{
public static final IntegerProperty VARIANT = IntegerProperty.create("variant", 0, 3);
protected final List<VoxelShape> variant_shapes;
public EdCatwalkTopAlignedBlock(long config, BlockBehaviour.Properties properties, final VoxelShape[] variant_shapes)
{
super(config, properties, variant_shapes[0]);
registerDefaultState(super.defaultBlockState().setValue(VARIANT, 0));
this.variant_shapes = VARIANT.getPossibleValues().stream().map(i->(i<variant_shapes.length) ? (variant_shapes[i]) : (Shapes.block())).collect(Collectors.toList());
}
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return variant_shapes.get(state.getValue(VARIANT)); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = adapted_state(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos());
if(context.getClickedFace() != Direction.UP) return state;
BlockState below = context.getLevel().getBlockState(context.getClickedPos().below());
if((state.getValue(VARIANT)==0) && (below.isFaceSturdy(context.getLevel(), context.getClickedPos().below(), Direction.UP))) return state.setValue(VARIANT, 3);
return state;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if(item != this.asItem()) return InteractionResult.PASS;
if(hit.getDirection().getAxis().isHorizontal()) return InteractionResult.PASS;
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(adapted_state(place_state, world, adjacent_pos), world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return adapted_state(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
// ---
private BlockState adapted_state(BlockState state, LevelAccessor world, BlockPos pos)
{
BlockState below = world.getBlockState(pos.below());
if((below == null) || (state == null)) return state;
if((below.getBlock() == ModContent.THICK_STEEL_POLE) || (below.getBlock() == ModContent.THICK_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 1);
if((below.getBlock() == ModContent.THIN_STEEL_POLE) || (below.getBlock() == ModContent.THIN_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 2);
return state;
}
}

View file

@ -1,195 +1,204 @@
/*
* @file EdChair.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class.
*/
package wile.engineersdecor.blocks;
import net.minecraft.entity.monster.piglin.PiglinEntity;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.block.AbstractBlock;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.World;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.*;
import net.minecraft.entity.monster.*;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.IPacket;
import net.minecraft.util.math.*;
import net.minecraft.util.*;
import net.minecraftforge.fml.network.FMLPlayMessages;
import net.minecraftforge.fml.network.NetworkHooks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import java.util.List;
import java.util.Random;
public class EdChair
{
private static boolean sitting_enabled = true;
private static double sitting_probability = 0.1;
private static double standup_probability = 0.01;
public static void on_config(boolean without_sitting, boolean without_mob_sitting, double sitting_probability_percent, double standup_probability_percent)
{
sitting_enabled = (!without_sitting);
sitting_probability = (without_sitting||without_mob_sitting) ? 0.0 : MathHelper.clamp(sitting_probability_percent/100, 0, 0.9);
standup_probability = (without_sitting||without_mob_sitting) ? 1.0 : MathHelper.clamp(standup_probability_percent/100, 1e-6, 1e-2);
ModConfig.log("Config chairs: sit:" + sitting_enabled + ", mob-sit: " + (sitting_probability*100) + "%, standup: " + (standup_probability) + "%.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class ChairBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public ChairBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, builder.randomTicks(), unrotatedAABBs); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(!sitting_enabled) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
EntityChair.sit(world, player, pos);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if(sitting_enabled && (Math.random() < sitting_probability) && (entity instanceof MobEntity)) EntityChair.sit(world, (LivingEntity)entity, pos);
}
@Override
@SuppressWarnings("deprecation")
public void tick(BlockState state, ServerWorld world, BlockPos pos, Random rnd)
{
if((!sitting_enabled) || (sitting_probability < 1e-6)) return;
final List<LivingEntity> entities = world.getEntitiesOfClass(MobEntity.class, new AxisAlignedBB(pos).inflate(2,1,2).expandTowards(0,1,0), e->true);
if(entities.isEmpty()) return;
int index = rnd.nextInt(entities.size());
if((index < 0) || (index >= entities.size())) return;
EntityChair.sit(world, entities.get(index), pos);
}
}
//--------------------------------------------------------------------------------------------------------------------
// Entity
//--------------------------------------------------------------------------------------------------------------------
public static class EntityChair extends Entity
{
public static final double x_offset = 0.5d;
public static final double y_offset = 0.4d;
public static final double z_offset = 0.5d;
private int t_sit = 0;
public BlockPos chair_pos = new BlockPos(0,0,0);
public EntityChair(EntityType<? extends Entity> entityType, World world)
{
super(entityType, world);
blocksBuilding=true;
setDeltaMovement(Vector3d.ZERO);
canUpdate(true);
noPhysics=true;
}
public EntityChair(World world)
{ this(ModContent.ET_CHAIR, world); }
public static EntityChair customClientFactory(FMLPlayMessages.SpawnEntity spkt, World world)
{ return new EntityChair(world); }
public IPacket<?> getAddEntityPacket()
{ return NetworkHooks.getEntitySpawningPacket(this); }
public static boolean accepts_mob(LivingEntity entity)
{
if(!(entity instanceof net.minecraft.entity.monster.MonsterEntity)) return false;
if((entity.getType().getDimensions().height > 2.5) || (entity.getType().getDimensions().height > 2.0)) return false;
if(entity instanceof ZombieEntity) return true;
if(entity instanceof ZombieVillagerEntity) return true;
if(entity instanceof ZombifiedPiglinEntity) return true;
if(entity instanceof PiglinEntity) return true;
if(entity instanceof HuskEntity) return true;
if(entity instanceof StrayEntity) return true;
if(entity instanceof SkeletonEntity) return true;
if(entity instanceof WitherSkeletonEntity) return true;
return false;
}
public static void sit(World world, LivingEntity sitter, BlockPos pos)
{
if(!sitting_enabled) return;
if((world==null) || (world.isClientSide) || (sitter==null) || (pos==null)) return;
if((!(sitter instanceof PlayerEntity)) && (!accepts_mob(sitter))) return;
if(!world.getEntitiesOfClass(EntityChair.class, new AxisAlignedBB(pos)).isEmpty()) return;
if(sitter.isVehicle() || (!sitter.isAlive()) || (sitter.isPassenger()) ) return;
if((!world.isEmptyBlock(pos.above())) || (!world.isEmptyBlock(pos.above(2)))) return;
boolean on_top_of_block_position = true;
boolean use_next_negative_y_position = false;
EntityChair chair = new EntityChair(world);
BlockPos chair_pos = chair.blockPosition();
chair.chair_pos = pos;
chair.t_sit = 5;
chair.xo = chair_pos.getX();
chair.yo = chair_pos.getY();
chair.zo = chair_pos.getZ();
chair.setPos(pos.getX()+x_offset,pos.getY()+y_offset,pos.getZ()+z_offset);
world.addFreshEntity(chair);
sitter.startRiding(chair, true);
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(CompoundNBT compound) {}
@Override
protected void addAdditionalSaveData(CompoundNBT compound) {}
@Override
public boolean isPushable()
{ return false; }
@Override
public double getPassengersRidingOffset()
{ return 0.0; }
@Override
public void tick()
{
if(level.isClientSide) return;
super.tick();
if(--t_sit > 0) return;
Entity sitter = getPassengers().isEmpty() ? null : getPassengers().get(0);
if((sitter==null) || (!sitter.isAlive())) {
this.remove();
return;
}
boolean abort = (!sitting_enabled);
final BlockState state = level.getBlockState(chair_pos);
if((state==null) || (!(state.getBlock() instanceof ChairBlock))) abort = true;
if(!level.isEmptyBlock(chair_pos.above())) abort = true;
if((!(sitter instanceof PlayerEntity)) && (Math.random() < standup_probability)) abort = true;
if(abort) {
for(Entity e:getPassengers()) {
if(e.isAlive()) e.stopRiding();
}
this.remove();
}
}
}
}
/*
* @file EdChair.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.monster.*;
import net.minecraft.world.entity.monster.piglin.Piglin;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fmllegacy.network.FMLPlayMessages;
import net.minecraftforge.fmllegacy.network.NetworkHooks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import java.util.List;
import java.util.Random;
public class EdChair
{
private static boolean sitting_enabled = true;
private static double sitting_probability = 0.1;
private static double standup_probability = 0.01;
public static void on_config(boolean without_sitting, boolean without_mob_sitting, double sitting_probability_percent, double standup_probability_percent)
{
sitting_enabled = (!without_sitting);
sitting_probability = (without_sitting||without_mob_sitting) ? 0.0 : Mth.clamp(sitting_probability_percent/100, 0, 0.9);
standup_probability = (without_sitting||without_mob_sitting) ? 1.0 : Mth.clamp(standup_probability_percent/100, 1e-6, 1e-2);
ModConfig.log("Config chairs: sit:" + sitting_enabled + ", mob-sit: " + (sitting_probability*100) + "%, standup: " + (standup_probability) + "%.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class ChairBlock extends StandardBlocks.HorizontalWaterLoggable
{
public ChairBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBs)
{ super(config, builder.randomTicks(), unrotatedAABBs); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(!sitting_enabled) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
EntityChair.sit(world, player, pos);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if(sitting_enabled && (Math.random() < sitting_probability) && (entity instanceof Mob)) EntityChair.sit(world, (LivingEntity)entity, pos);
}
@Override
@SuppressWarnings("deprecation")
public void tick(BlockState state, ServerLevel world, BlockPos pos, Random rnd)
{
if((!sitting_enabled) || (sitting_probability < 1e-6)) return;
final List<Mob> entities = world.getEntitiesOfClass(Mob.class, new AABB(pos).inflate(2,1,2).expandTowards(0,1,0), e->true);
if(entities.isEmpty()) return;
int index = rnd.nextInt(entities.size());
if((index < 0) || (index >= entities.size())) return;
EntityChair.sit(world, entities.get(index), pos);
}
}
//--------------------------------------------------------------------------------------------------------------------
// Entity
//--------------------------------------------------------------------------------------------------------------------
public static class EntityChair extends Entity
{
public static final double x_offset = 0.5d;
public static final double y_offset = 0.4d;
public static final double z_offset = 0.5d;
private int t_sit = 0;
public BlockPos chair_pos = new BlockPos(0,0,0);
public EntityChair(EntityType<? extends Entity> entityType, Level world)
{
super(entityType, world);
blocksBuilding=true;
setDeltaMovement(Vec3.ZERO);
canUpdate(true);
noPhysics=true;
}
public EntityChair(Level world)
{ this(ModContent.ET_CHAIR, world); }
public static EntityChair customClientFactory(FMLPlayMessages.SpawnEntity spkt, Level world)
{ return new EntityChair(world); }
public Packet<?> getAddEntityPacket()
{ return NetworkHooks.getEntitySpawningPacket(this); }
public static boolean accepts_mob(LivingEntity entity)
{
if(!(entity instanceof Monster)) return false;
if((entity.getType().getDimensions().height > 2.5) || (entity.getType().getDimensions().height > 2.0)) return false;
if(entity instanceof Zombie) return true;
if(entity instanceof ZombieVillager) return true;
if(entity instanceof ZombifiedPiglin) return true;
if(entity instanceof Piglin) return true;
if(entity instanceof Husk) return true;
if(entity instanceof Stray) return true;
if(entity instanceof Skeleton) return true;
if(entity instanceof WitherSkeleton) return true;
return false;
}
public static void sit(Level world, LivingEntity sitter, BlockPos pos)
{
if(!sitting_enabled) return;
if((world==null) || (world.isClientSide) || (sitter==null) || (pos==null)) return;
if((!(sitter instanceof Player)) && (!accepts_mob(sitter))) return;
if(!world.getEntitiesOfClass(EntityChair.class, new AABB(pos)).isEmpty()) return;
if(sitter.isVehicle() || (!sitter.isAlive()) || (sitter.isPassenger()) ) return;
if((!world.isEmptyBlock(pos.above())) || (!world.isEmptyBlock(pos.above(2)))) return;
boolean on_top_of_block_position = true;
boolean use_next_negative_y_position = false;
EntityChair chair = new EntityChair(world);
BlockPos chair_pos = chair.blockPosition();
chair.chair_pos = pos;
chair.t_sit = 5;
chair.xo = chair_pos.getX();
chair.yo = chair_pos.getY();
chair.zo = chair_pos.getZ();
chair.setPos(pos.getX()+x_offset,pos.getY()+y_offset,pos.getZ()+z_offset);
world.addFreshEntity(chair);
sitter.startRiding(chair, true);
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {}
@Override
protected void addAdditionalSaveData(CompoundTag compound) {}
@Override
public boolean isPushable()
{ return false; }
@Override
public double getPassengersRidingOffset()
{ return 0.0; }
@Override
public void tick()
{
if(level.isClientSide) return;
super.tick();
if(--t_sit > 0) return;
Entity sitter = getPassengers().isEmpty() ? null : getPassengers().get(0);
if((sitter==null) || (!sitter.isAlive())) {
this.remove(RemovalReason.DISCARDED);
return;
}
boolean abort = (!sitting_enabled);
final BlockState state = level.getBlockState(chair_pos);
if((state==null) || (!(state.getBlock() instanceof ChairBlock))) abort = true;
if(!level.isEmptyBlock(chair_pos.above())) abort = true;
if((!(sitter instanceof Player)) && (Math.random() < standup_probability)) abort = true;
if(abort) {
for(Entity e:getPassengers()) {
if(e.isAlive()) e.stopRiding();
}
this.remove(RemovalReason.DISCARDED);
}
}
}
}

View file

@ -1,98 +1,101 @@
/*
* @file EdChimneyBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for smoking chimneys.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.Random;
public class EdChimneyBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final IntegerProperty POWER = BlockStateProperties.POWER;
public EdChimneyBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public EdChimneyBlock(long config, AbstractBlock.Properties builder)
{
this(config, builder, new AxisAlignedBB(0,0,0,1,1,1));
registerDefaultState(super.defaultBlockState().setValue(POWER, 0)); // no smoke in JEI
}
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(POWER); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
if(state==null) return state;
int p = context.getLevel().getBestNeighborSignal(context.getClickedPos());
return state.setValue(POWER, p==0 ? 5 : p);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{ world.setBlock(pos, state.setValue(POWER, (state.getValue(POWER)+1) & 0xf), 1|2); return ActionResultType.sidedSuccess(world.isClientSide()); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
int p = world.getBestNeighborSignal(pos);
if(p != state.getValue(POWER)) world.setBlock(pos, state.setValue(POWER, p), 2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if(state.getBlock() != this) return;
final int p = state.getValue(POWER);
if(p==0) return;
int end = 1+rnd.nextInt(10) * p / 15;
for(int i=0; i<end; ++i) {
double rv = rnd.nextDouble() * p / 5;
world.addParticle(
(rv > 0.7 ? ParticleTypes.LARGE_SMOKE : (rv>0.4 ? ParticleTypes.SMOKE : ParticleTypes.CAMPFIRE_COSY_SMOKE)),
0.5+pos.getX()+(rnd.nextDouble()*0.2),
0.9+pos.getY()+(rnd.nextDouble()*0.1),
0.5+pos.getZ()+(rnd.nextDouble()*0.2),
-0.02 + rnd.nextDouble()*0.04,
+0.05 + rnd.nextDouble()*0.1,
-0.02 + rnd.nextDouble()*0.04
);
}
}
}
/*
* @file EdChimneyBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for smoking chimneys.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.Random;
public class EdChimneyBlock extends StandardBlocks.Cutout
{
public static final IntegerProperty POWER = BlockStateProperties.POWER;
public EdChimneyBlock(long config, BlockBehaviour.Properties properties, AABB aabb)
{ super(config, properties, aabb); }
public EdChimneyBlock(long config, BlockBehaviour.Properties builder)
{
this(config, builder, new AABB(0,0,0,1,1,1));
registerDefaultState(super.defaultBlockState().setValue(POWER, 0)); // no smoke in JEI
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(POWER); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
if(state==null) return state;
int p = context.getLevel().getBestNeighborSignal(context.getClickedPos());
return state.setValue(POWER, p==0 ? 5 : p);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{ world.setBlock(pos, state.setValue(POWER, (state.getValue(POWER)+1) & 0xf), 1|2); return InteractionResult.sidedSuccess(world.isClientSide()); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
int p = world.getBestNeighborSignal(pos);
if(p != state.getValue(POWER)) world.setBlock(pos, state.setValue(POWER, p), 2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if(state.getBlock() != this) return;
final int p = state.getValue(POWER);
if(p==0) return;
int end = 1+rnd.nextInt(10) * p / 15;
for(int i=0; i<end; ++i) {
double rv = rnd.nextDouble() * p / 5;
world.addParticle(
(rv > 0.7 ? ParticleTypes.LARGE_SMOKE : (rv>0.4 ? ParticleTypes.SMOKE : ParticleTypes.CAMPFIRE_COSY_SMOKE)),
0.5+pos.getX()+(rnd.nextDouble()*0.2),
0.9+pos.getY()+(rnd.nextDouble()*0.1),
0.5+pos.getZ()+(rnd.nextDouble()*0.2),
-0.02 + rnd.nextDouble()*0.04,
+0.05 + rnd.nextDouble()*0.1,
-0.02 + rnd.nextDouble()*0.04
);
}
}
}

View file

@ -1,36 +1,37 @@
/*
* @file EdChimneyTrunkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof block with chimney trunk, only straight.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.state.properties.Half;
import net.minecraft.state.properties.StairsShape;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import javax.annotation.Nullable;
public class EdChimneyTrunkBlock extends EdRoofBlock implements IDecorBlock
{
public EdChimneyTrunkBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties.dynamicShape(), VoxelShapes.empty(), VoxelShapes.empty()); }
public EdChimneyTrunkBlock(long config, AbstractBlock.Properties properties, VoxelShape add, VoxelShape cut)
{ super(config, properties, add, cut); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
return (state==null) ? (state) : (state.setValue(EdRoofBlock.SHAPE, StairsShape.STRAIGHT).setValue(EdRoofBlock.HALF, Half.BOTTOM));
}
}
/*
* @file EdChimneyTrunkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof block with chimney trunk, only straight.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
public class EdChimneyTrunkBlock extends EdRoofBlock
{
public EdChimneyTrunkBlock(long config, BlockBehaviour.Properties properties)
{ super(config, properties.dynamicShape(), Shapes.empty(), Shapes.empty()); }
public EdChimneyTrunkBlock(long config, BlockBehaviour.Properties properties, VoxelShape add, VoxelShape cut)
{ super(config, properties, add, cut); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
return (state==null) ? (state) : (state.setValue(EdRoofBlock.SHAPE, StairsShape.STRAIGHT).setValue(EdRoofBlock.HALF, Half.BOTTOM));
}
}

View file

@ -1,71 +1,72 @@
/*
* @file EdCornerOrnamentedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block for corner/quoin ornamentation.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.world.World;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCornerOrnamentedBlock extends DecorBlock.Directed
{
protected final HashSet<Block> compatible_blocks;
public EdCornerOrnamentedBlock(long config, AbstractBlock.Properties properties, Block[] assigned_wall_blocks)
{
super(config, properties, Auxiliaries.getPixeledAABB(0,0,0,16,16,16));
compatible_blocks = new HashSet<Block>(Arrays.asList(assigned_wall_blocks));
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
final World world = context.getLevel();
final BlockPos pos = context.getClickedPos();
// 1. Placement as below/above for corners, or placement adjacent horizontally if up/down facing.
for(Direction adj: Direction.values()) {
BlockState state = world.getBlockState(pos.relative(adj));
if(state.getBlock() != this) continue;
Direction facing = state.getValue(FACING);
if(facing.getAxis().isHorizontal() == (adj.getAxis().isVertical())) {
return super.getStateForPlacement(context).setValue(FACING, state.getValue(FACING));
}
}
// 2. By Player look angles with minimum horizontal diagonal deviation.
{
Direction facing = Direction.WEST;
final Vector2f look = context.getPlayer().getRotationVector();
final Direction hit_face = context.getClickedFace();
if((context.getClickedFace()==Direction.DOWN) && (look.x <= -60)) {
facing = Direction.DOWN;
} else if((context.getClickedFace()==Direction.UP) && (look.x >= 60)) {
facing = Direction.UP;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45) <= 45) {
facing = Direction.NORTH;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45+90) <= 45) {
facing = Direction.EAST;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45+180) <= 45) {
facing = Direction.SOUTH;
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
}
}
/*
* @file EdCornerOrnamentedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block for corner/quoin ornamentation.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec2;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
public class EdCornerOrnamentedBlock extends StandardBlocks.Directed
{
protected final HashSet<Block> compatible_blocks;
public EdCornerOrnamentedBlock(long config, BlockBehaviour.Properties properties, Block[] assigned_wall_blocks)
{
super(config, properties, Auxiliaries.getPixeledAABB(0,0,0,16,16,16));
compatible_blocks = new HashSet<>(Arrays.asList(assigned_wall_blocks));
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
final Level world = context.getLevel();
final BlockPos pos = context.getClickedPos();
// 1. Placement as below/above for corners, or placement adjacent horizontally if up/down facing.
for(Direction adj: Direction.values()) {
BlockState state = world.getBlockState(pos.relative(adj));
if(state.getBlock() != this) continue;
Direction facing = state.getValue(FACING);
if(facing.getAxis().isHorizontal() == (adj.getAxis().isVertical())) {
return super.getStateForPlacement(context).setValue(FACING, state.getValue(FACING));
}
}
// 2. By Player look angles with minimum horizontal diagonal deviation.
{
Direction facing = Direction.WEST;
final Vec2 look = context.getPlayer().getRotationVector();
final Direction hit_face = context.getClickedFace();
if((context.getClickedFace()==Direction.DOWN) && (look.x <= -60)) {
facing = Direction.DOWN;
} else if((context.getClickedFace()==Direction.UP) && (look.x >= 60)) {
facing = Direction.UP;
} else if(Mth.degreesDifferenceAbs(look.y, 45) <= 45) {
facing = Direction.NORTH;
} else if(Mth.degreesDifferenceAbs(look.y, 45+90) <= 45) {
facing = Direction.EAST;
} else if(Mth.degreesDifferenceAbs(look.y, 45+180) <= 45) {
facing = Direction.SOUTH;
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +0,0 @@
/*
* @file EdDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Blocks representing centered doors opening by sliding
* to the sides.
*/
package wile.engineersdecor.blocks;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.block.*;
import wile.engineersdecor.libmc.blocks.StandardDoorBlock;
public class EdDoorBlock extends StandardDoorBlock implements IDecorBlock
{
public EdDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] open_aabbs_top, AxisAlignedBB[] open_aabbs_bottom, AxisAlignedBB[] closed_aabbs_top, AxisAlignedBB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_aabbs_top, open_aabbs_bottom, closed_aabbs_top, closed_aabbs_bottom, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB open_aabb, AxisAlignedBB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_aabb, closed_aabb, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
}

View file

@ -1,156 +1,165 @@
/*
* @file EdDoubleGateBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Gate blocks that can be one or two segments high.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.pathfinding.PathType;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
public class EdDoubleGateBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final IntegerProperty SEGMENT = IntegerProperty.create("segment", 0, 1);
public static final BooleanProperty OPEN = FenceGateBlock.OPEN;
public static final int SEGMENT_LOWER = 0;
public static final int SEGMENT_UPPER = 1;
protected final ArrayList<VoxelShape> collision_shapes_;
public EdDoubleGateBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ this(config, properties, new AxisAlignedBB[]{aabb}); }
public EdDoubleGateBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{
super(config, properties, aabbs);
AxisAlignedBB[] caabbs = new AxisAlignedBB[aabbs.length];
for(int i=0; i<caabbs.length; ++i) caabbs[i] = aabbs[i].expandTowards(0, 0.5, 0);
collision_shapes_ = new ArrayList<VoxelShape>(Arrays.asList(
VoxelShapes.block(),
VoxelShapes.block(),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.NORTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.SOUTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.WEST, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.EAST, true)),
VoxelShapes.block(),
VoxelShapes.block()
));
}
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return state.getValue(OPEN) ? VoxelShapes.empty() : collision_shapes_.get(state.getValue(HORIZONTAL_FACING).get3DDataValue() & 0x7); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SEGMENT).add(OPEN); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return getInitialState(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos()); }
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return getInitialState(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if((rayTraceResult.getDirection()==Direction.UP) || (rayTraceResult.getDirection()==Direction.DOWN) && (player.getItemInHand(hand).getItem()==this.asItem())) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
final boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open),2|8|16);
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
final BlockState adjacent = world.getBlockState(pos.below());
if(adjacent.getBlock()==this) world.setBlock(pos.below(), adjacent.setValue(OPEN, open), 2|8|16);
} else {
final BlockState adjacent = world.getBlockState(pos.above());
if(adjacent.getBlock()==this) world.setBlock(pos.above(), adjacent.setValue(OPEN, open), 2|8|16);
}
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if(world.isClientSide) return;
boolean powered = false;
BlockState adjacent;
BlockPos adjacent_pos;
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
adjacent_pos = pos.below();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.getSignal(pos.above(), Direction.UP) > 0) {
powered = true;
} else if((adjacent!=null) && (world.hasNeighborSignal(pos.below(2)))) {
powered = true;
}
} else {
adjacent_pos = pos.above();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.hasNeighborSignal(pos)) {
powered = true;
} else if((adjacent!=null) && (world.getSignal(pos.above(2), Direction.UP) > 0)) {
powered = true;
}
}
boolean sound = false;
if(powered != state.getValue(OPEN)) {
world.setBlock(pos, state.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if((adjacent != null) && (powered != adjacent.getValue(OPEN))) {
world.setBlock(adjacent_pos, adjacent.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if(sound) {
world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
}
}
// -------------------------------------------------------------------------------------------------------------------
private BlockState getInitialState(BlockState state, IWorld world, BlockPos pos)
{
final BlockState down = world.getBlockState(pos.below());
if(down.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_UPPER).setValue(OPEN, down.getValue(OPEN)).setValue(HORIZONTAL_FACING, down.getValue(HORIZONTAL_FACING));
final BlockState up = world.getBlockState(pos.above());
if(up.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, up.getValue(OPEN)).setValue(HORIZONTAL_FACING, up.getValue(HORIZONTAL_FACING));
return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, false);
}
}
/*
* @file EdDoubleGateBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Gate blocks that can be one or two segments high.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
public class EdDoubleGateBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final IntegerProperty SEGMENT = IntegerProperty.create("segment", 0, 1);
public static final BooleanProperty OPEN = FenceGateBlock.OPEN;
public static final int SEGMENT_LOWER = 0;
public static final int SEGMENT_UPPER = 1;
protected final ArrayList<VoxelShape> collision_shapes_;
public EdDoubleGateBlock(long config, BlockBehaviour.Properties properties, AABB aabb)
{ this(config, properties, new AABB[]{aabb}); }
public EdDoubleGateBlock(long config, BlockBehaviour.Properties properties, AABB[] aabbs)
{
super(config, properties, aabbs);
AABB[] caabbs = new AABB[aabbs.length];
for(int i=0; i<caabbs.length; ++i) caabbs[i] = aabbs[i].expandTowards(0, 0.5, 0);
collision_shapes_ = new ArrayList<>(Arrays.asList(
Shapes.block(),
Shapes.block(),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.NORTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.SOUTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.WEST, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.EAST, true)),
Shapes.block(),
Shapes.block()
));
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return state.getValue(OPEN) ? Shapes.empty() : collision_shapes_.get(state.getValue(HORIZONTAL_FACING).get3DDataValue() & 0x7); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SEGMENT).add(OPEN); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return getInitialState(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos()); }
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return getInitialState(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if((rayTraceResult.getDirection()==Direction.UP) || (rayTraceResult.getDirection()==Direction.DOWN) && (player.getItemInHand(hand).getItem()==this.asItem())) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
final boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open),2|8|16);
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
final BlockState adjacent = world.getBlockState(pos.below());
if(adjacent.getBlock()==this) world.setBlock(pos.below(), adjacent.setValue(OPEN, open), 2|8|16);
} else {
final BlockState adjacent = world.getBlockState(pos.above());
if(adjacent.getBlock()==this) world.setBlock(pos.above(), adjacent.setValue(OPEN, open), 2|8|16);
}
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if(world.isClientSide) return;
boolean powered = false;
BlockState adjacent;
BlockPos adjacent_pos;
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
adjacent_pos = pos.below();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.getSignal(pos.above(), Direction.UP) > 0) {
powered = true;
} else if((adjacent!=null) && (world.hasNeighborSignal(pos.below(2)))) {
powered = true;
}
} else {
adjacent_pos = pos.above();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.hasNeighborSignal(pos)) {
powered = true;
} else if((adjacent!=null) && (world.getSignal(pos.above(2), Direction.UP) > 0)) {
powered = true;
}
}
boolean sound = false;
if(powered != state.getValue(OPEN)) {
world.setBlock(pos, state.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if((adjacent != null) && (powered != adjacent.getValue(OPEN))) {
world.setBlock(adjacent_pos, adjacent.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if(sound) {
world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
}
}
// -------------------------------------------------------------------------------------------------------------------
private BlockState getInitialState(BlockState state, LevelAccessor world, BlockPos pos)
{
final BlockState down = world.getBlockState(pos.below());
if(down.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_UPPER).setValue(OPEN, down.getValue(OPEN)).setValue(HORIZONTAL_FACING, down.getValue(HORIZONTAL_FACING));
final BlockState up = world.getBlockState(pos.above());
if(up.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, up.getValue(OPEN)).setValue(HORIZONTAL_FACING, up.getValue(HORIZONTAL_FACING));
return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, false);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,30 @@
/*
* @file EdFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardFenceBlock;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader;
import net.minecraft.block.*;
public class EdFenceBlock extends StandardFenceBlock implements IDecorBlock
{
public EdFenceBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
public EdFenceBlock(long config, AbstractBlock.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{ super(config, properties, pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); }
@Override
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{ return ((facingState.getBlock()) instanceof EdDoubleGateBlock) || super.attachesTo(facingState, world, facingPos, side); }
}
/*
* @file EdFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.libmc.blocks.StandardFenceBlock;
public class EdFenceBlock extends StandardFenceBlock
{
public EdFenceBlock(long config, BlockBehaviour.Properties properties)
{ super(config, properties); }
public EdFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{ super(config, properties, pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); }
@Override
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{ return ((facingState.getBlock()) instanceof EdDoubleGateBlock) || super.attachesTo(facingState, world, facingPos, side); }
}

View file

@ -1,72 +1,73 @@
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class EdFloorGratingBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public EdFloorGratingBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if(!(entity instanceof ItemEntity)) return;
final boolean colliding = ((entity.position().y-pos.getY()) > 0.7);
if(colliding || (entity.getDeltaMovement().y() > 0)) {
double x = pos.getX() + 0.5;
double y = MathHelper.clamp(entity.position().y-0.3, pos.getY(), pos.getY()+0.6);
double z = pos.getZ() + 0.5;
double vx = entity.getDeltaMovement().x();
double vy = entity.getDeltaMovement().y();
double vz = entity.getDeltaMovement().z();
if(colliding) {
vx = 0;
vy = -0.3;
vz = 0;
if((entity.position().y-pos.getY()) > 0.8) y = pos.getY() + 0.6;
entity.xo = x+0.1;
entity.yo = y+0.1;
entity.zo = z+0.1;
}
vy = MathHelper.clamp(vy, -0.3, 0);
entity.setDeltaMovement(vx, vy, vz);
entity.fallDistance = 0;
entity.teleportTo(x,y,z);
}
}
}
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
public class EdFloorGratingBlock extends StandardBlocks.WaterLoggable
{
public EdFloorGratingBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if(!(entity instanceof ItemEntity)) return;
final boolean colliding = ((entity.position().y-pos.getY()) > 0.7);
if(colliding || (entity.getDeltaMovement().y() > 0)) {
double x = pos.getX() + 0.5;
double y = Mth.clamp(entity.position().y-0.3, pos.getY(), pos.getY()+0.6);
double z = pos.getZ() + 0.5;
double vx = entity.getDeltaMovement().x();
double vy = entity.getDeltaMovement().y();
double vz = entity.getDeltaMovement().z();
if(colliding) {
vx = 0;
vy = -0.3;
vz = 0;
if((entity.position().y-pos.getY()) > 0.8) y = pos.getY() + 0.6;
entity.xo = x+0.1;
entity.yo = y+0.1;
entity.zo = z+0.1;
}
vy = Mth.clamp(vy, -0.3, 0);
entity.setDeltaMovement(vx, vy, vz);
entity.fallDistance = 0;
entity.teleportTo(x,y,z);
}
}
}

View file

@ -1,428 +1,399 @@
/*
* @file EdFluidBarrel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple fluid tank with a built-in gauge.
*/
package wile.engineersdecor.blocks;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.util.*;
import net.minecraft.world.IWorldReader;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdFluidBarrel
{
//--------------------------------------------------------------------------------------------------------------------
// Config
//--------------------------------------------------------------------------------------------------------------------
private static int capacity_ = 12000;
private static int item_fluid_handler_transfer_rate_ = 1000;
private static int tile_fluid_handler_transfer_rate_ = 1000;
public static void on_config(int tank_capacity, int transfer_rate)
{
capacity_ = MathHelper.clamp(tank_capacity, 2000, 64000);
tile_fluid_handler_transfer_rate_ = MathHelper.clamp(tank_capacity, 50, 4096);
item_fluid_handler_transfer_rate_ = tile_fluid_handler_transfer_rate_;
ModConfig.log("Config fluid barrel: capacity:" + capacity_ + "mb, transfer-rate:" + tile_fluid_handler_transfer_rate_ + "mb/t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock, StandardBlocks.IBlockItemFactory
{
public static final int FILL_LEVEL_MAX = 4;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidBarrelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP).setValue(FILL_LEVEL, 0));
}
// IBlockItemFactory ----------------------------------------------------------------------------
@Override
public BlockItem getBlockItem(Block block, Item.Properties builder)
{ return new FluidBarrelItem(block, builder); }
// IStandardBlock --------------------------------------------------------------------------------
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, final TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidBarrelTileEntity)) return stacks;
ItemStack stack = new ItemStack(this, 1);
CompoundNBT te_nbt = ((FluidBarrelTileEntity) te).clear_getnbt();
if(!te_nbt.isEmpty()) {
CompoundNBT nbt = new CompoundNBT();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
return stacks;
}
// Block/IForgeBlock -----------------------------------------------------------------------------
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(final ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(
(!(stack.getItem() instanceof FluidBarrelItem)) ||
(Auxiliaries.Tooltip.helpCondition())
) {
super.appendHoverText(stack, world, tooltip, flag); return;
}
FluidStack fs = FluidBarrelItem.getFluid(stack);
if(!fs.isEmpty()) {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip", new Object[] {
Integer.toString(fs.getAmount()),
Integer.toString(capacity_),
new TranslationTextComponent(fs.getTranslationKey())
}));
} else {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip.empty", new Object[] {
"0",
Integer.toString(capacity_),
}));
}
if(!Auxiliaries.Tooltip.extendedTipCondition()) {
super.appendHoverText(stack, world, tooltip, flag);
}
}
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdFluidBarrel.FluidBarrelTileEntity(); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
if(!context.getPlayer().isShiftKeyDown()) state = state.setValue(FACING, Direction.UP);
return state;
}
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundNBT te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return;
((FluidBarrelTileEntity)te).readnbt(te_nbt);
((FluidBarrelTileEntity)te).setChanged();
world.getBlockTicks().scheduleTick(pos, this, 4);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(player.getItemInHand(hand).getItem() == asItem()) return ActionResultType.PASS; // Pass that to block placement.
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return ActionResultType.FAIL;
if(!((FluidBarrelTileEntity)te).handlePlayerInteraction(state, world, pos, player, hand)) return ActionResultType.PASS;
world.getBlockTicks().scheduleTick(pos, this, 4);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return 0;
return (int)MathHelper.clamp(((FluidBarrelTileEntity)te).getNormalizedFillLevel() * 15, 0, 15);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelTileEntity extends TileEntity implements ICapabilityProvider, ITickableTileEntity
{
private final int TICK_INTERVAL = 10;
private int tick_timer_ = 0;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
public FluidBarrelTileEntity()
{ this(ModContent.TET_FLUID_BARREL); }
public FluidBarrelTileEntity(TileEntityType<?> te_type)
{
super(te_type);
tank_ = new Fluidics.Tank(capacity_);
tank_.setInteractionNotifier((t,d)->on_tank_changed());
fluid_handler_ = tank_.createFluidHandler();
}
public void readnbt(CompoundNBT nbt)
{ tank_.load(nbt); }
public CompoundNBT writenbt(CompoundNBT nbt)
{ tank_.save(nbt); return nbt; }
public boolean handlePlayerInteraction(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand)
{
if(world.isClientSide()) return false;
{
Tuple<Fluid,Integer> transferred = Fluidics.manualTrackedFluidHandlerInteraction(world, pos, null, player, hand);
if(transferred==null) {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundCategory.BLOCKS, 0.2f, 0.02f);
} else if(transferred.getB() > 0) {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA: SoundEvents.BUCKET_EMPTY;
world.playSound(null, pos, se, SoundCategory.BLOCKS, 1f, 1f);
} else {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_FILL_LAVA : SoundEvents.BUCKET_FILL;
world.playSound(null, pos, se, SoundCategory.BLOCKS, 1f, 1f);
}
}
{
int vol = tank_.getFluidAmount();
int cap = tank_.getCapacity();
String name = (new TranslationTextComponent(tank_.getFluid().getTranslationKey())).getString();
if((vol>0) && (cap>0)) {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status", new Object[]{
Integer.toString(vol), Integer.toString(cap), name
}));
} else {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status.empty", new Object[]{
Integer.toString(vol), Integer.toString(cap)
}));
}
}
return true;
}
public double getNormalizedFillLevel()
{ return (tank_.isEmpty()) ? (0) : ((double)tank_.getFluidAmount()/(double)tank_.getCapacity()); }
protected void on_tank_changed()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); return writenbt(nbt); }
@Override
public void setRemoved()
{ super.setRemoved(); fluid_handler_.invalidate(); }
public CompoundNBT clear_getnbt()
{ return tank_.save(new CompoundNBT()); }
// ICapabilityProvider --------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickableTileEntity --------------------------------------------------------------------
private boolean transfer_down()
{
if(tank_.isEmpty()) return false;
final IFluidHandler fh = FluidUtil.getFluidHandler(level, worldPosition.below(), Direction.UP).orElse(null);
if(fh==null) return false;
final FluidStack fs = tank_.getFluid().copy();
if(fs.getAmount() > tile_fluid_handler_transfer_rate_) fs.setAmount(tile_fluid_handler_transfer_rate_);
final int nfilled = fh.fill(fs, FluidAction.EXECUTE);
if(nfilled <= 0) return false;
tank_.drain(nfilled, FluidAction.EXECUTE);
return true;
}
public void tick()
{
if((level.isClientSide()) || (--tick_timer_>=0)) return;
tick_timer_ = TICK_INTERVAL;
final BlockState state = getBlockState();
final Block block = state.getBlock();
if(!(block instanceof FluidBarrelBlock)) return;
if(state.getValue(FluidBarrelBlock.FACING).getAxis().isVertical()) transfer_down(); // tick_timer_ ==> 1 if something was transferred, otherwise no need to waste CPU
double norm_level = getNormalizedFillLevel();
int fill_level = (norm_level <= 0) ? 0 : ((int)MathHelper.clamp((norm_level * FluidBarrelBlock.FILL_LEVEL_MAX)+0.5, 1, FluidBarrelBlock.FILL_LEVEL_MAX));
if(fill_level != state.getValue(FluidBarrelBlock.FILL_LEVEL)) {
level.setBlock(worldPosition, state.setValue(FluidBarrelBlock.FILL_LEVEL, fill_level), 2);
level.updateNeighborsAt(worldPosition, block);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Block item
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelItem extends BlockItem
{
public FluidBarrelItem(Block block, Item.Properties builder)
{ super(block, builder); }
private static CompoundNBT read_fluid_nbt(ItemStack stack)
{
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return new CompoundNBT();
final CompoundNBT nbt = stack.getTag().getCompound("tedata");
if(!nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) return new CompoundNBT();
return nbt.getCompound("tank");
}
private static void write_fluid_nbt(ItemStack stack, CompoundNBT fluid_nbt)
{
if((fluid_nbt==null) || (fluid_nbt.isEmpty())) {
if((!stack.hasTag()) || (!stack.getTag().contains("tedata", Constants.NBT.TAG_COMPOUND))) return;
final CompoundNBT tag = stack.getTag();
final CompoundNBT tedata = tag.getCompound("tedata");
if(tedata.contains("tank")) tedata.remove("tank");
if(tedata.isEmpty()) tag.remove("tedata");
stack.setTag(tag.isEmpty() ? null : tag);
} else {
CompoundNBT tag = stack.getTag();
if(tag==null) tag = new CompoundNBT();
CompoundNBT tedata = tag.getCompound("tedata");
if(tedata==null) tedata = new CompoundNBT();
tedata.put("tank", fluid_nbt);
tag.put("tedata", tedata);
stack.setTag(tag);
}
}
public static FluidStack getFluid(ItemStack stack)
{
final CompoundNBT nbt = read_fluid_nbt(stack);
return (nbt.isEmpty()) ? (FluidStack.EMPTY) : (FluidStack.loadFluidStackFromNBT(nbt));
}
public static ItemStack setFluid(ItemStack stack, FluidStack fs)
{ write_fluid_nbt(stack, fs.writeToNBT(new CompoundNBT())); return stack; }
@Override
public int getItemStackLimit(ItemStack stack)
{ return (!getFluid(stack).isEmpty()) ? 1 : super.getItemStackLimit(stack); }
@Override
public boolean showDurabilityBar(ItemStack stack)
{ return (!getFluid(stack).isEmpty()); }
@Override
public double getDurabilityForDisplay(ItemStack stack)
{ return 1.0 - MathHelper.clamp(((double)(getFluid(stack).getAmount()))/((double)capacity_), 0.0, 1.0); }
@Override
public int getRGBDurabilityForDisplay(ItemStack stack)
{ return 0x336633; }
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundNBT nbt)
{ return new Fluidics.FluidContainerItemCapabilityWrapper(stack, capacity_, item_fluid_handler_transfer_rate_, (s)->read_fluid_nbt(s), (s,n)->write_fluid_nbt(s,n), e->true); }
@Override
public boolean hasContainerItem(ItemStack stack)
{ return (stack.getCount()==1) && (!getFluid(stack).isEmpty()); }
@Override
public ItemStack getContainerItem(ItemStack stack)
{
if(stack.getCount()!=1) return ItemStack.EMPTY;
FluidStack fs = getFluid(stack);
if(fs.getAmount() > 1000) fs.shrink(1000); else fs = FluidStack.EMPTY;
return setFluid(stack, fs);
}
}
}
/*
* @file EdFluidBarrel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple fluid tank with a built-in gauge.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdFluidBarrel
{
//--------------------------------------------------------------------------------------------------------------------
// Config
//--------------------------------------------------------------------------------------------------------------------
private static int capacity_ = 12000;
private static int item_fluid_handler_transfer_rate_ = 1000;
private static int tile_fluid_handler_transfer_rate_ = 1000;
public static void on_config(int tank_capacity, int transfer_rate)
{
capacity_ = Mth.clamp(tank_capacity, 2000, 64000);
tile_fluid_handler_transfer_rate_ = Mth.clamp(tank_capacity, 50, 4096);
item_fluid_handler_transfer_rate_ = tile_fluid_handler_transfer_rate_;
ModConfig.log("Config fluid barrel: capacity:" + capacity_ + "mb, transfer-rate:" + tile_fluid_handler_transfer_rate_ + "mb/t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelBlock extends StandardBlocks.DirectedWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<FluidBarrelTileEntity>, StandardBlocks.IBlockItemFactory
{
public static final int FILL_LEVEL_MAX = 4;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidBarrelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP).setValue(FILL_LEVEL, 0));
}
@Nullable
@Override
public BlockEntityType<EdFluidBarrel.FluidBarrelTileEntity> getBlockEntityType()
{ return ModContent.TET_FLUID_BARREL; }
@Override
public BlockItem getBlockItem(Block block, Item.Properties builder)
{ return new FluidBarrelItem(block, builder); }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, final BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidBarrelTileEntity)) return stacks;
ItemStack stack = new ItemStack(this, 1);
CompoundTag te_nbt = ((FluidBarrelTileEntity) te).clear_getnbt();
if(!te_nbt.isEmpty()) {
CompoundTag nbt = new CompoundTag();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
return stacks;
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(final ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if((!(stack.getItem() instanceof FluidBarrelItem)) || (Auxiliaries.Tooltip.helpCondition())) {
super.appendHoverText(stack, world, tooltip, flag); return;
}
FluidStack fs = FluidBarrelItem.getFluid(stack);
if(!fs.isEmpty()) {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip", Integer.toString(fs.getAmount()), Integer.toString(capacity_), new TranslatableComponent(fs.getTranslationKey())));
} else {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip.empty", "0", Integer.toString(capacity_)));
}
if(!Auxiliaries.Tooltip.extendedTipCondition()) {
super.appendHoverText(stack, world, tooltip, flag);
}
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
if(!context.getPlayer().isShiftKeyDown()) state = state.setValue(FACING, Direction.UP);
return state;
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundTag te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return;
((FluidBarrelTileEntity)te).readnbt(te_nbt);
te.setChanged();
world.getBlockTicks().scheduleTick(pos, this, 4);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(player.getItemInHand(hand).getItem() == asItem()) return InteractionResult.PASS; // Pass that to block placement.
if(world.isClientSide()) return InteractionResult.SUCCESS;
if(!(world.getBlockEntity(pos) instanceof final FluidBarrelTileEntity te)) return InteractionResult.FAIL;
if(!te.handlePlayerInteraction(state, world, pos, player, hand)) return InteractionResult.PASS;
world.getBlockTicks().scheduleTick(pos, this, 4);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return 0;
return (int)Mth.clamp(((FluidBarrelTileEntity)te).getNormalizedFillLevel() * 15, 0, 15);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelTileEntity extends StandardEntityBlocks.StandardBlockEntity implements ICapabilityProvider
{
private final int TICK_INTERVAL = 10;
private int tick_timer_ = 0;
private final Fluidics.Tank tank_ = (new Fluidics.Tank(capacity_)).setInteractionNotifier((t,d)->on_tank_changed());
private final LazyOptional<IFluidHandler> fluid_handler_ = tank_.createFluidHandler();
public FluidBarrelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_FLUID_BARREL, pos, state); }
public void readnbt(CompoundTag nbt)
{ tank_.load(nbt); }
public CompoundTag writenbt(CompoundTag nbt)
{ tank_.save(nbt); return nbt; }
public boolean handlePlayerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand)
{
if(world.isClientSide()) return false;
{
Tuple<Fluid,Integer> transferred = Fluidics.manualTrackedFluidHandlerInteraction(world, pos, null, player, hand);
if(transferred==null) {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundSource.BLOCKS, 0.2f, 0.02f);
} else if(transferred.getB() > 0) {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA: SoundEvents.BUCKET_EMPTY;
world.playSound(null, pos, se, SoundSource.BLOCKS, 1f, 1f);
} else {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_FILL_LAVA : SoundEvents.BUCKET_FILL;
world.playSound(null, pos, se, SoundSource.BLOCKS, 1f, 1f);
}
}
{
int vol = tank_.getFluidAmount();
int cap = tank_.getCapacity();
String name = (new TranslatableComponent(tank_.getFluid().getTranslationKey())).getString();
if((vol>0) && (cap>0)) {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status", Integer.toString(vol), Integer.toString(cap), name));
} else {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status.empty", Integer.toString(vol), Integer.toString(cap)));
}
}
return true;
}
public double getNormalizedFillLevel()
{ return (tank_.isEmpty()) ? (0) : ((double)tank_.getFluidAmount()/(double)tank_.getCapacity()); }
protected void on_tank_changed()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); return writenbt(nbt); }
@Override
public void setRemoved()
{ super.setRemoved(); fluid_handler_.invalidate(); }
public CompoundTag clear_getnbt()
{ return tank_.save(new CompoundTag()); }
// ICapabilityProvider --------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// Tick --------------------------------------------------------------------
private boolean transfer_down()
{
if(tank_.isEmpty()) return false;
final IFluidHandler fh = Fluidics.handler(level, worldPosition.below(), Direction.UP);
if(fh==null) return false;
final FluidStack fs = tank_.getFluid().copy();
if(fs.getAmount() > tile_fluid_handler_transfer_rate_) fs.setAmount(tile_fluid_handler_transfer_rate_);
final int nfilled = fh.fill(fs, IFluidHandler.FluidAction.EXECUTE);
if(nfilled <= 0) return false;
tank_.drain(nfilled, IFluidHandler.FluidAction.EXECUTE);
return true;
}
public void tick()
{
if((level.isClientSide()) || (--tick_timer_>=0)) return;
tick_timer_ = TICK_INTERVAL;
final BlockState state = getBlockState();
final Block block = state.getBlock();
if(!(block instanceof FluidBarrelBlock)) return;
if(state.getValue(FluidBarrelBlock.FACING).getAxis().isVertical()) transfer_down(); // tick_timer_ ==> 1 if something was transferred, otherwise no need to waste CPU
double norm_level = getNormalizedFillLevel();
int fill_level = (norm_level <= 0) ? 0 : ((int)Mth.clamp((norm_level * FluidBarrelBlock.FILL_LEVEL_MAX)+0.5, 1, FluidBarrelBlock.FILL_LEVEL_MAX));
if(fill_level != state.getValue(FluidBarrelBlock.FILL_LEVEL)) {
level.setBlock(worldPosition, state.setValue(FluidBarrelBlock.FILL_LEVEL, fill_level), 2);
level.updateNeighborsAt(worldPosition, block);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Block item
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelItem extends BlockItem
{
public FluidBarrelItem(Block block, Item.Properties builder)
{ super(block, builder); }
private static CompoundTag read_fluid_nbt(ItemStack stack)
{
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return new CompoundTag();
final CompoundTag nbt = stack.getTag().getCompound("tedata");
if(!nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) return new CompoundTag();
return nbt.getCompound("tank");
}
private static void write_fluid_nbt(ItemStack stack, CompoundTag fluid_nbt)
{
if((fluid_nbt==null) || (fluid_nbt.isEmpty())) {
if((!stack.hasTag()) || (!stack.getTag().contains("tedata", Constants.NBT.TAG_COMPOUND))) return;
final CompoundTag tag = stack.getTag();
final CompoundTag tedata = tag.getCompound("tedata");
if(tedata.contains("tank")) tedata.remove("tank");
if(tedata.isEmpty()) tag.remove("tedata");
stack.setTag(tag.isEmpty() ? null : tag);
} else {
CompoundTag tag = stack.getTag();
if(tag==null) tag = new CompoundTag();
CompoundTag tedata = tag.getCompound("tedata");
if(tedata==null) tedata = new CompoundTag();
tedata.put("tank", fluid_nbt);
tag.put("tedata", tedata);
stack.setTag(tag);
}
}
public static FluidStack getFluid(ItemStack stack)
{
final CompoundTag nbt = read_fluid_nbt(stack);
return (nbt.isEmpty()) ? (FluidStack.EMPTY) : (FluidStack.loadFluidStackFromNBT(nbt));
}
public static ItemStack setFluid(ItemStack stack, FluidStack fs)
{ write_fluid_nbt(stack, fs.writeToNBT(new CompoundTag())); return stack; }
@Override
public int getItemStackLimit(ItemStack stack)
{ return (!getFluid(stack).isEmpty()) ? 1 : super.getItemStackLimit(stack); }
@Override
public boolean showDurabilityBar(ItemStack stack)
{ return (!getFluid(stack).isEmpty()); }
@Override
public double getDurabilityForDisplay(ItemStack stack)
{ return 1.0 - Mth.clamp(((double)(getFluid(stack).getAmount()))/((double)capacity_), 0.0, 1.0); }
@Override
public int getRGBDurabilityForDisplay(ItemStack stack)
{ return 0x336633; }
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt)
{ return new Fluidics.FluidContainerItemCapabilityWrapper(stack, capacity_, item_fluid_handler_transfer_rate_, FluidBarrelItem::read_fluid_nbt, FluidBarrelItem::write_fluid_nbt, e->true); }
@Override
public boolean hasContainerItem(ItemStack stack)
{ return (stack.getCount()==1) && (!getFluid(stack).isEmpty()); }
@Override
public ItemStack getContainerItem(ItemStack stack)
{
if(stack.getCount()!=1) return ItemStack.EMPTY;
FluidStack fs = getFluid(stack);
if(fs.getAmount() > 1000) fs.shrink(1000); else fs = FluidStack.EMPTY;
return setFluid(stack, fs);
}
}
}

View file

@ -1,416 +1,400 @@
/*
* @file EdFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.world.IWorldReader;
import net.minecraft.block.*;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.FluidState;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nullable;
import java.util.*;
public class EdFluidFunnel
{
private static boolean with_device_fluid_handler_collection = false;
public static void on_config(boolean with_tank_fluid_collection)
{
with_device_fluid_handler_collection = with_tank_fluid_collection;
ModConfig.log("Config fluid funnel: tank-fluid-collection:" + with_device_fluid_handler_collection + ".");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final int FILL_LEVEL_MAX = 3;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidFunnelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(FILL_LEVEL, 0); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new FluidFunnelTileEntity(); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.getValue(FILL_LEVEL)*5), 0, 15); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundNBT te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return;
((FluidFunnelTileEntity)te).readnbt(te_nbt);
((FluidFunnelTileEntity)te).setChanged();
world.setBlockAndUpdate(pos, state.setValue(FILL_LEVEL, 0));
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, final TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidFunnelTileEntity)) return stacks;
if(!explosion) {
ItemStack stack = new ItemStack(this, 1);
CompoundNBT te_nbt = new CompoundNBT();
((FluidFunnelTileEntity)te).writenbt(te_nbt);
if(!te_nbt.isEmpty()) {
CompoundNBT nbt = new CompoundNBT();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
} else {
stacks.add(new ItemStack(this, 1));
}
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(world.isClientSide) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return ActionResultType.FAIL;
return FluidUtil.interactWithFluidHandler(player, hand, world, pos, rayTraceResult.getDirection()) ? ActionResultType.CONSUME : ActionResultType.FAIL;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{ TileEntity te = world.getBlockEntity(pos); if(te instanceof FluidFunnelTileEntity) ((FluidFunnelTileEntity)te).block_changed(); }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ZERO;
private ArrayList<Vector3i> search_offsets_ = null;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
public FluidFunnelTileEntity()
{ this(ModContent.TET_SMALL_FLUID_FUNNEL); }
public FluidFunnelTileEntity(TileEntityType<?> te_type)
{
super(te_type);
tank_ = new Fluidics.Tank(TANK_CAPACITY, 0, TANK_CAPACITY);
fluid_handler_ = tank_.createOutputFluidHandler();
}
public void readnbt(CompoundNBT nbt)
{
tank_.load(nbt);
}
public void writenbt(CompoundNBT nbt)
{
tank_.save(nbt);
}
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; }
// TileEntity -----------------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
fluid_handler_.invalidate();
}
// ICapabilityProvider / Output flow handler ----------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickableTileEntity --------------------------------------------------------------------------------
private FluidState get_fluidstate(BlockPos pos)
{
final Block collection_block = level.getBlockState(pos).getBlock();
if((!(collection_block instanceof IFluidBlock)) && (!(collection_block instanceof FlowingFluidBlock)) && (!(collection_block instanceof IWaterLoggable))) {
return Fluids.EMPTY.defaultFluidState();
}
return level.getFluidState(pos);
}
private boolean try_pick(BlockPos pos, FluidState fluidstate)
{
if(!fluidstate.isSource()) return false;
IFluidHandler hnd = FluidUtil.getFluidHandler(level, pos, null).orElse(null);
FluidStack fs;
if(hnd != null) {
fs = hnd.drain(TANK_CAPACITY, FluidAction.EXECUTE); // IFluidBlock
} else {
fs = new FluidStack(fluidstate.getType(), 1000);
BlockState state = level.getBlockState(pos);
if(state instanceof IBucketPickupHandler) {
((IBucketPickupHandler)state).takeLiquid(level, pos, state);
} else if((state.getBlock() instanceof IWaterLoggable) && (state.hasProperty(BlockStateProperties.WATERLOGGED))) {
level.setBlock(pos, state.setValue(BlockStateProperties.WATERLOGGED, false), 1|2);
} else {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 1|2); // ok we can't leave the block, that would be an infinite source of an unknown fluid.
}
}
if((fs==null) || (fs.isEmpty())) return false; // it's marked nonnull but I don't trust every modder - including meself ...
if(tank_.isEmpty()) {
tank_.setFluid(fs.copy());
} else if(tank_.isFluidEqual(fs)) {
tank_.fill(fs, FluidAction.EXECUTE);
} else {
return false;
}
return true;
}
private boolean can_pick(BlockPos pos, FluidState fluidstate)
{
if(fluidstate.isSource()) return true;
IFluidHandler hnd = FluidUtil.getFluidHandler(level, pos, null).orElse(null);
if(hnd == null) return false;
FluidStack fs = hnd.drain(TANK_CAPACITY, FluidAction.SIMULATE); // don't trust that everyone returns nonnull
return ((fs!=null) && (!fs.isEmpty())) && (fluidstate.getType().isSame(fs.getFluid()));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vector3i(0, 1, 0)); // up first
{
ArrayList<Vector3i> ofs = new ArrayList<Vector3i>(Arrays.asList(new Vector3i(-1, 0, 0), new Vector3i( 1, 0, 0), new Vector3i( 0, 0,-1), new Vector3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vector3i> ofs = new ArrayList<Vector3i>(Arrays.asList(new Vector3i(-1, 1, 0), new Vector3i( 1, 1, 0), new Vector3i( 0, 1,-1), new Vector3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(final BlockPos collection_pos)
{
FluidState collection_fluidstate = get_fluidstate(collection_pos);
if(collection_fluidstate.isEmpty()) return false;
Fluid fluid_to_collect = collection_fluidstate.getType();
if((!tank_.isEmpty()) && (!tank_.getFluid().getFluid().isSame(fluid_to_collect))) return false;
if(try_pick(collection_pos, collection_fluidstate)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
if((last_pick_pos_==null) || (last_pick_pos_.distSqr(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<BlockPos>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.offset(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
FluidState fluidstate = get_fluidstate(p);
if(fluidstate.getType().isSame(fluid_to_collect)) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
FluidState fs = get_fluidstate(pos.above());
if(!can_pick(pos.above(), fs)) break;
fluidstate = fs;
pos = pos.above();
trail.push(pos);
}
}
if(try_pick(pos, fluidstate)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && level.random.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos)));
//String s = new String(); for(BlockPos p:checked) s += "\n" + p; println(s);
if(intensive_search_counter_ > 2) level.removeBlock(pos, false);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
final BlockState funnel_state = level.getBlockState(worldPosition);
if(!(funnel_state.getBlock() instanceof FluidFunnelBlock)) return;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.getFluidAmount() <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!level.hasNeighborSignal(worldPosition)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = worldPosition.above();
TileEntity te = with_device_fluid_handler_collection ? (level.getBlockEntity(worldPosition.above())) : (null);
if(te != null) {
IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, Direction.DOWN).orElse(null);
if(fh == null) {
te = null;
} else if(tank_.isEmpty()) {
FluidStack fs = fh.drain(1000, FluidAction.EXECUTE);
if((fs!=null) && (!fs.isEmpty())) tank_.setFluid(fs.copy());
dirty = true;
} else if (!tank_.isFull()) {
FluidStack todrain = new FluidStack(tank_.getFluid(), Math.min(tank_.getCapacity()-tank_.getFluidAmount(), 1000));
tank_.fill(fh.drain(todrain, FluidAction.EXECUTE), FluidAction.EXECUTE);
dirty = true;
}
}
if(te==null) {
if(try_collect(worldPosition.above())) dirty = true;
}
}
}
// Gravity fluid transfer
if((tank_.getFluidAmount() >= 1000)) {
IFluidHandler fh = FluidUtil.getFluidHandler(level, worldPosition.below(), Direction.UP).orElse(null);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid().getFluid(), 1000);
int nfilled = MathHelper.clamp(fh.fill(fs, FluidAction.EXECUTE), 0, 1000);
tank_.drain(nfilled);
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (MathHelper.clamp(tank_.getFluidAmount()/1000,0, FluidFunnelBlock.FILL_LEVEL_MAX));
if(funnel_state.getValue(FluidFunnelBlock.FILL_LEVEL) != fill_level) level.setBlock(worldPosition, funnel_state.setValue(FluidFunnelBlock.FILL_LEVEL, fill_level), 2|16);
if(dirty) setChanged();
}
}
}
/*
* @file EdFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nullable;
import java.util.*;
public class EdFluidFunnel
{
private static boolean with_device_fluid_handler_collection = false;
public static void on_config(boolean with_tank_fluid_collection)
{
with_device_fluid_handler_collection = with_tank_fluid_collection;
ModConfig.log("Config fluid funnel: tank-fluid-collection:" + with_device_fluid_handler_collection + ".");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelBlock extends StandardBlocks.Cutout implements StandardEntityBlocks.IStandardEntityBlock<FluidFunnelTileEntity>
{
public static final int FILL_LEVEL_MAX = 3;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidFunnelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Nullable
@Override
public BlockEntityType<EdFluidFunnel.FluidFunnelTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_FLUID_FUNNEL; }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(FILL_LEVEL, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{ return Mth.clamp((state.getValue(FILL_LEVEL)*5), 0, 15); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundTag te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return;
((FluidFunnelTileEntity)te).readnbt(te_nbt);
te.setChanged();
world.setBlockAndUpdate(pos, state.setValue(FILL_LEVEL, 0));
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, final BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidFunnelTileEntity)) return stacks;
if(!explosion) {
ItemStack stack = new ItemStack(this, 1);
CompoundTag te_nbt = new CompoundTag();
((FluidFunnelTileEntity)te).writenbt(te_nbt);
if(!te_nbt.isEmpty()) {
CompoundTag nbt = new CompoundTag();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
} else {
stacks.add(new ItemStack(this, 1));
}
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(world.isClientSide) return InteractionResult.SUCCESS;
return (world.getBlockEntity(pos) instanceof FluidFunnelTileEntity) && Fluidics.manualFluidHandlerInteraction(player, hand, world, pos, rayTraceResult.getDirection()) ? InteractionResult.CONSUME : InteractionResult.FAIL;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{ BlockEntity te = world.getBlockEntity(pos); if(te instanceof FluidFunnelTileEntity) ((FluidFunnelTileEntity)te).block_changed(); }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ZERO;
private ArrayList<Vec3i> search_offsets_ = null;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, 0, TANK_CAPACITY);
private final LazyOptional<IFluidHandler> fluid_handler_ = tank_.createOutputFluidHandler();
public FluidFunnelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_FLUID_FUNNEL, pos, state); }
public void readnbt(CompoundTag nbt)
{
tank_.load(nbt);
}
public void writenbt(CompoundTag nbt)
{
tank_.save(nbt);
}
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; }
// BlockEntity -----------------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
fluid_handler_.invalidate();
}
// ICapabilityProvider / Output flow handler ----------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// -----------------------------------------------------------------------------------------------------------------
private FluidState get_fluidstate(BlockPos pos)
{
return level.getFluidState(pos);
}
private boolean try_pick(BlockPos pos, FluidState fluidstate)
{
if(!fluidstate.isSource()) return false;
IFluidHandler hnd = Fluidics.handler(level, pos, null);
FluidStack fs;
if(hnd != null) {
fs = hnd.drain(TANK_CAPACITY, IFluidHandler.FluidAction.EXECUTE); // IFluidBlock
} else {
fs = new FluidStack(fluidstate.getType(), 1000);
BlockState state = level.getBlockState(pos);
if(state.hasProperty(BlockStateProperties.WATERLOGGED)) {
level.setBlock(pos, state.setValue(BlockStateProperties.WATERLOGGED, false), 1|2);
} else {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 1|2); // ok we can't leave the block, that would be an infinite source of an unknown fluid.
}
}
if((fs==null) || (fs.isEmpty())) return false; // it's marked nonnull but I don't trust every modder - including meself ...
if(tank_.isEmpty()) {
tank_.setFluid(fs.copy());
} else if(tank_.isFluidEqual(fs)) {
tank_.fill(fs, IFluidHandler.FluidAction.EXECUTE);
} else {
return false;
}
return true;
}
private boolean can_pick(BlockPos pos, FluidState fluidstate)
{
if(fluidstate.isSource()) return true;
final IFluidHandler hnd = Fluidics.handler(level, pos, null);
if(hnd == null) return false;
final FluidStack fs = hnd.drain(TANK_CAPACITY, IFluidHandler.FluidAction.SIMULATE); // don't trust that everyone returns nonnull
return ((fs!=null) && (!fs.isEmpty())) && (fluidstate.getType().isSame(fs.getFluid()));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vec3i(0, 1, 0)); // up first
{
ArrayList<Vec3i> ofs = new ArrayList<>(Arrays.asList(new Vec3i(-1, 0, 0), new Vec3i( 1, 0, 0), new Vec3i( 0, 0,-1), new Vec3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vec3i> ofs = new ArrayList<>(Arrays.asList(new Vec3i(-1, 1, 0), new Vec3i( 1, 1, 0), new Vec3i( 0, 1,-1), new Vec3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(final BlockPos collection_pos)
{
FluidState collection_fluidstate = get_fluidstate(collection_pos);
if(collection_fluidstate.isEmpty()) return false;
Fluid fluid_to_collect = collection_fluidstate.getType();
if((!tank_.isEmpty()) && (!tank_.getFluid().getFluid().isSame(fluid_to_collect))) return false;
if(try_pick(collection_pos, collection_fluidstate)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
if((last_pick_pos_==null) || (last_pick_pos_.distSqr(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.offset(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
FluidState fluidstate = get_fluidstate(p);
if(fluidstate.getType().isSame(fluid_to_collect)) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
FluidState fs = get_fluidstate(pos.above());
if(!can_pick(pos.above(), fs)) break;
fluidstate = fs;
pos = pos.above();
trail.push(pos);
}
}
if(try_pick(pos, fluidstate)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && level.random.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos)));
//String s = new String(); for(BlockPos p:checked) s += "\n" + p; println(s);
if(intensive_search_counter_ > 2) level.removeBlock(pos, false);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
final BlockState funnel_state = level.getBlockState(worldPosition);
if(!(funnel_state.getBlock() instanceof FluidFunnelBlock)) return;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.getFluidAmount() <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!level.hasNeighborSignal(worldPosition)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = worldPosition.above();
BlockEntity te = with_device_fluid_handler_collection ? (level.getBlockEntity(worldPosition.above())) : (null);
if(te != null) {
IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, Direction.DOWN).orElse(null);
if(fh == null) {
te = null;
} else if(tank_.isEmpty()) {
FluidStack fs = fh.drain(1000, IFluidHandler.FluidAction.EXECUTE);
if((fs!=null) && (!fs.isEmpty())) tank_.setFluid(fs.copy());
dirty = true;
} else if (!tank_.isFull()) {
FluidStack todrain = new FluidStack(tank_.getFluid(), Math.min(tank_.getCapacity()-tank_.getFluidAmount(), 1000));
tank_.fill(fh.drain(todrain, IFluidHandler.FluidAction.EXECUTE), IFluidHandler.FluidAction.EXECUTE);
dirty = true;
}
}
if(te==null) {
if(try_collect(worldPosition.above())) dirty = true;
}
}
}
// Gravity fluid transfer
if((tank_.getFluidAmount() >= 1000)) {
final IFluidHandler fh = Fluidics.handler(level, worldPosition.below(), Direction.UP);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid().getFluid(), 1000);
int nfilled = Mth.clamp(fh.fill(fs, IFluidHandler.FluidAction.EXECUTE), 0, 1000);
tank_.drain(nfilled);
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (Mth.clamp(tank_.getFluidAmount()/1000,0, FluidFunnelBlock.FILL_LEVEL_MAX));
if(funnel_state.getValue(FluidFunnelBlock.FILL_LEVEL) != fill_level) level.setBlock(worldPosition, funnel_state.setValue(FluidFunnelBlock.FILL_LEVEL, fill_level), 2|16);
if(dirty) setChanged();
}
}
}

View file

@ -1,394 +1,397 @@
/*
* @file EdFreezer.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small highly insulated stone liquification furnace
* (magmatic phase).
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraft.block.*;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.item.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.fluid.Fluids;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.ModContent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class EdFreezer
{
public static void on_config(int consumption, int cooldown_per_second)
{ FreezerTileEntity.on_config(consumption, cooldown_per_second); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerBlock extends DecorBlock.Horizontal implements IDecorBlock
{
public static final int PHASE_MAX = 4;
public static final IntegerProperty PHASE = IntegerProperty.create("phase", 0, PHASE_MAX);
public FreezerBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PHASE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(PHASE, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.getValue(PHASE)*4), 0, 15); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdFreezer.FreezerTileEntity(); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FreezerTileEntity)) return stacks;
((FreezerTileEntity)te).reset_process();
stacks.add(new ItemStack(this, 1));
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(player.isShiftKeyDown()) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
FreezerTileEntity te = getTe(world, pos);
if(te==null) return ActionResultType.FAIL;
final ItemStack stack = player.getItemInHand(hand);
boolean dirty = false;
if(Fluidics.manualFluidHandlerInteraction(world, pos, null, player, hand)) {
world.playSound(null, pos, SoundEvents.BUCKET_EMPTY, SoundCategory.BLOCKS, 0.5f, 1.4f);
return ActionResultType.CONSUME;
}
if(stack.getItem()==Items.WATER_BUCKET) {
return ActionResultType.CONSUME; // would be already handled
} else if(stack.isEmpty()) {
ItemStack ice = te.getIceItem(true);
if(!ice.isEmpty()) {
player.addItem(ice);
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundCategory.BLOCKS, 0.3f, 1.1f);
} else {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundCategory.BLOCKS, 0.2f, 0.02f);
}
return ActionResultType.CONSUME;
} else {
return ActionResultType.PASS;
}
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{}
@Nullable
private FreezerTileEntity getTe(World world, BlockPos pos)
{ final TileEntity te=world.getBlockEntity(pos); return (!(te instanceof FreezerTileEntity)) ? (null) : ((FreezerTileEntity)te); }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerTileEntity extends TileEntity implements ITickableTileEntity, IEnergyStorage, ICapabilityProvider
{
public static final int TICK_INTERVAL = 20;
public static final int MAX_FLUID_LEVEL = 2000;
public static final int MAX_ENERGY_BUFFER = 32000;
public static final int MAX_ENERGY_TRANSFER = 8192;
public static final int TANK_CAPACITY = 2000;
public static final int DEFAULT_ENERGY_CONSUMPTION = 92;
public static final int DEFAULT_COOLDOWN_RATE = 2;
public static final int PHASE_EMPTY = 0;
public static final int PHASE_WATER = 1;
public static final int PHASE_ICE = 2;
public static final int PHASE_PACKEDICE = 3;
public static final int PHASE_BLUEICE = 4;
private static int energy_consumption = DEFAULT_ENERGY_CONSUMPTION;
private static int cooldown_rate = DEFAULT_COOLDOWN_RATE;
private static int reheat_rate = 1;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, TANK_CAPACITY, TANK_CAPACITY, fs->fs.getFluid()==Fluids.WATER);
private int tick_timer_;
private int energy_stored_;
private int progress_;
private boolean force_block_update_;
public static void on_config(int consumption, int cooldown_per_second)
{
energy_consumption = MathHelper.clamp(consumption, 8, 4096);
cooldown_rate = MathHelper.clamp(cooldown_per_second, 1, 5);
reheat_rate = MathHelper.clamp(cooldown_per_second/2, 1, 5);
ModConfig.log("Config freezer energy consumption:" + energy_consumption + "rf/t, cooldown-rate: " + cooldown_rate + "%/s.");
}
public FreezerTileEntity()
{ this(ModContent.TET_FREEZER); }
public FreezerTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public int progress()
{ return progress_; }
public int phase()
{
if(tank_.getFluidAmount() < 1000) return PHASE_EMPTY;
if(progress_ >= 100) return PHASE_BLUEICE;
if(progress_ >= 70) return PHASE_PACKEDICE;
if(progress_ >= 30) return PHASE_ICE;
return PHASE_WATER;
}
public ItemStack getIceItem(boolean extract)
{
ItemStack stack;
switch(phase()) {
case PHASE_ICE: stack = new ItemStack(Items.ICE); break;
case PHASE_PACKEDICE: stack = new ItemStack(Items.PACKED_ICE); break;
case PHASE_BLUEICE: stack = new ItemStack(Items.BLUE_ICE); break;
default: return ItemStack.EMPTY;
}
if(extract) reset_process();
return stack;
}
public int comparator_signal()
{ return phase() * 4; }
protected void reset_process()
{
force_block_update_ = true;
tank_.drain(1000);
tick_timer_ = 0;
progress_ = 0;
}
public void readnbt(CompoundNBT nbt)
{
energy_stored_ = nbt.getInt("energy");
progress_ = nbt.getInt("progress");
tank_.load(nbt);
}
protected void writenbt(CompoundNBT nbt)
{
nbt.putInt("energy", MathHelper.clamp(energy_stored_,0 , MAX_ENERGY_BUFFER));
nbt.putInt("progress", MathHelper.clamp(progress_,0 , 100));
tank_.save(nbt);
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
// IItemHandler --------------------------------------------------------------------------------
private final LazyOptional<IItemHandler> item_handler_ = LazyOptional.of(() -> (IItemHandler)new FreezerItemHandler(this));
protected static class FreezerItemHandler implements IItemHandler
{
private final FreezerTileEntity te;
FreezerItemHandler(FreezerTileEntity te)
{ this.te = te; }
@Override
public int getSlots()
{ return 1; }
@Override
public int getSlotLimit(int index)
{ return 1; }
@Override
public boolean isItemValid(int slot, @Nonnull ItemStack stack)
{ return false; }
@Override
@Nonnull
public ItemStack getStackInSlot(int index)
{ return (index!=0) ? ItemStack.EMPTY : te.getIceItem(false); }
@Override
@Nonnull
public ItemStack insertItem(int index, @Nonnull ItemStack stack, boolean simulate)
{ return ItemStack.EMPTY; }
@Override
@Nonnull
public ItemStack extractItem(int index, int amount, boolean simulate)
{ return te.getIceItem(!simulate); }
}
// IFluidHandler --------------------------------------------------------------------------------
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(tank_));
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return MAX_ENERGY_BUFFER; }
@Override
public int getEnergyStored()
{ return energy_stored_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
if(energy_stored_ >= MAX_ENERGY_BUFFER) return 0;
int n = Math.min(maxReceive, (MAX_ENERGY_BUFFER - energy_stored_));
if(n > MAX_ENERGY_TRANSFER) n = MAX_ENERGY_TRANSFER;
if(!simulate) {energy_stored_ += n; setChanged(); }
return n;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(level.isClientSide) return;
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof FreezerBlock)) return;
boolean dirty = false;
final int last_phase = phase();
if(tank_.getFluidAmount() < 1000) {
progress_ = 0;
} else if((energy_stored_ <= 0) || (level.hasNeighborSignal(worldPosition))) {
progress_ = MathHelper.clamp(progress_-reheat_rate, 0,100);
} else if(progress_ >= 100) {
progress_ = 100;
energy_stored_ = MathHelper.clamp(energy_stored_-((energy_consumption*TICK_INTERVAL)/20), 0, MAX_ENERGY_BUFFER);
} else {
energy_stored_ = MathHelper.clamp(energy_stored_-(energy_consumption*TICK_INTERVAL), 0, MAX_ENERGY_BUFFER);
progress_ = MathHelper.clamp(progress_+cooldown_rate, 0, 100);
}
int new_phase = phase();
if(new_phase > last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundCategory.BLOCKS, 0.2f, 0.7f);
} else if(new_phase < last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundCategory.BLOCKS, 0.2f, 0.7f);
}
// Block state
if((force_block_update_ || (state.getValue(FreezerBlock.PHASE) != new_phase))) {
state = state.setValue(FreezerBlock.PHASE, new_phase);
level.setBlock(worldPosition, state,3|16);
level.updateNeighborsAt(getBlockPos(), state.getBlock());
force_block_update_ = false;
}
if(dirty) setChanged();
}
}
}
/*
* @file EdFreezer.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small highly insulated stone liquification furnace
* (magmatic phase).
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class EdFreezer
{
public static void on_config(int consumption, int cooldown_per_second)
{ FreezerTileEntity.on_config(consumption, cooldown_per_second); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerBlock extends StandardBlocks.Horizontal implements StandardEntityBlocks.IStandardEntityBlock<FreezerTileEntity>
{
public static final int PHASE_MAX = 4;
public static final IntegerProperty PHASE = IntegerProperty.create("phase", 0, PHASE_MAX);
public FreezerBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Nullable
@Override
public BlockEntityType<EdFreezer.FreezerTileEntity> getBlockEntityType()
{ return ModContent.TET_FREEZER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PHASE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(PHASE, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{ return Mth.clamp((state.getValue(PHASE)*4), 0, 15); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FreezerTileEntity)) return stacks;
((FreezerTileEntity)te).reset_process();
stacks.add(new ItemStack(this, 1));
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(player.isShiftKeyDown()) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
FreezerTileEntity te = getTe(world, pos);
if(te==null) return InteractionResult.FAIL;
final ItemStack stack = player.getItemInHand(hand);
boolean dirty = false;
if(Fluidics.manualFluidHandlerInteraction(world, pos, null, player, hand)) {
world.playSound(null, pos, SoundEvents.BUCKET_EMPTY, SoundSource.BLOCKS, 0.5f, 1.4f);
return InteractionResult.CONSUME;
}
if(stack.getItem()==Items.WATER_BUCKET) {
return InteractionResult.CONSUME; // would be already handled
} else if(stack.isEmpty()) {
ItemStack ice = te.getIceItem(true);
if(!ice.isEmpty()) {
player.addItem(ice);
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.BLOCKS, 0.3f, 1.1f);
} else {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundSource.BLOCKS, 0.2f, 0.02f);
}
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{}
@Nullable
private FreezerTileEntity getTe(Level world, BlockPos pos)
{ final BlockEntity te=world.getBlockEntity(pos); return (!(te instanceof FreezerTileEntity)) ? (null) : ((FreezerTileEntity)te); }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerTileEntity extends StandardEntityBlocks.StandardBlockEntity implements IEnergyStorage
{
public static final int TICK_INTERVAL = 20;
public static final int MAX_FLUID_LEVEL = 2000;
public static final int MAX_ENERGY_BUFFER = 32000;
public static final int MAX_ENERGY_TRANSFER = 8192;
public static final int TANK_CAPACITY = 2000;
public static final int DEFAULT_ENERGY_CONSUMPTION = 92;
public static final int DEFAULT_COOLDOWN_RATE = 2;
public static final int PHASE_EMPTY = 0;
public static final int PHASE_WATER = 1;
public static final int PHASE_ICE = 2;
public static final int PHASE_PACKEDICE = 3;
public static final int PHASE_BLUEICE = 4;
private static int energy_consumption = DEFAULT_ENERGY_CONSUMPTION;
private static int cooldown_rate = DEFAULT_COOLDOWN_RATE;
private static int reheat_rate = 1;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, TANK_CAPACITY, TANK_CAPACITY, fs->fs.getFluid()==Fluids.WATER);
private int tick_timer_;
private int energy_stored_;
private int progress_;
private boolean force_block_update_;
public static void on_config(int consumption, int cooldown_per_second)
{
energy_consumption = Mth.clamp(consumption, 8, 4096);
cooldown_rate = Mth.clamp(cooldown_per_second, 1, 5);
reheat_rate = Mth.clamp(cooldown_per_second/2, 1, 5);
ModConfig.log("Config freezer energy consumption:" + energy_consumption + "rf/t, cooldown-rate: " + cooldown_rate + "%/s.");
}
public FreezerTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_FREEZER, pos, state); }
public int progress()
{ return progress_; }
public int phase()
{
if(tank_.getFluidAmount() < 1000) return PHASE_EMPTY;
if(progress_ >= 100) return PHASE_BLUEICE;
if(progress_ >= 70) return PHASE_PACKEDICE;
if(progress_ >= 30) return PHASE_ICE;
return PHASE_WATER;
}
public ItemStack getIceItem(boolean extract)
{
ItemStack stack;
switch(phase()) {
case PHASE_ICE: stack = new ItemStack(Items.ICE); break;
case PHASE_PACKEDICE: stack = new ItemStack(Items.PACKED_ICE); break;
case PHASE_BLUEICE: stack = new ItemStack(Items.BLUE_ICE); break;
default: return ItemStack.EMPTY;
}
if(extract) reset_process();
return stack;
}
public int comparator_signal()
{ return phase() * 4; }
protected void reset_process()
{
force_block_update_ = true;
tank_.drain(1000);
tick_timer_ = 0;
progress_ = 0;
}
public void readnbt(CompoundTag nbt)
{
energy_stored_ = nbt.getInt("energy");
progress_ = nbt.getInt("progress");
tank_.load(nbt);
}
protected void writenbt(CompoundTag nbt)
{
nbt.putInt("energy", Mth.clamp(energy_stored_,0 , MAX_ENERGY_BUFFER));
nbt.putInt("progress", Mth.clamp(progress_,0 , 100));
tank_.save(nbt);
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
// IItemHandler --------------------------------------------------------------------------------
private final LazyOptional<IItemHandler> item_handler_ = LazyOptional.of(() -> new FreezerItemHandler(this));
protected static class FreezerItemHandler implements IItemHandler
{
private final FreezerTileEntity te;
FreezerItemHandler(FreezerTileEntity te)
{ this.te = te; }
@Override
public int getSlots()
{ return 1; }
@Override
public int getSlotLimit(int index)
{ return 1; }
@Override
public boolean isItemValid(int slot, @Nonnull ItemStack stack)
{ return false; }
@Override
@Nonnull
public ItemStack getStackInSlot(int index)
{ return (index!=0) ? ItemStack.EMPTY : te.getIceItem(false); }
@Override
@Nonnull
public ItemStack insertItem(int index, @Nonnull ItemStack stack, boolean simulate)
{ return ItemStack.EMPTY; }
@Override
@Nonnull
public ItemStack extractItem(int index, int amount, boolean simulate)
{ return te.getIceItem(!simulate); }
}
// IFluidHandler --------------------------------------------------------------------------------
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(tank_));
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return MAX_ENERGY_BUFFER; }
@Override
public int getEnergyStored()
{ return energy_stored_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
if(energy_stored_ >= MAX_ENERGY_BUFFER) return 0;
int n = Math.min(maxReceive, (MAX_ENERGY_BUFFER - energy_stored_));
if(n > MAX_ENERGY_TRANSFER) n = MAX_ENERGY_TRANSFER;
if(!simulate) {energy_stored_ += n; setChanged(); }
return n;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(level.isClientSide) return;
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof FreezerBlock)) return;
boolean dirty = false;
final int last_phase = phase();
if(tank_.getFluidAmount() < 1000) {
progress_ = 0;
} else if((energy_stored_ <= 0) || (level.hasNeighborSignal(worldPosition))) {
progress_ = Mth.clamp(progress_-reheat_rate, 0,100);
} else if(progress_ >= 100) {
progress_ = 100;
energy_stored_ = Mth.clamp(energy_stored_-((energy_consumption*TICK_INTERVAL)/20), 0, MAX_ENERGY_BUFFER);
} else {
energy_stored_ = Mth.clamp(energy_stored_-(energy_consumption*TICK_INTERVAL), 0, MAX_ENERGY_BUFFER);
progress_ = Mth.clamp(progress_+cooldown_rate, 0, 100);
}
int new_phase = phase();
if(new_phase > last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundSource.BLOCKS, 0.2f, 0.7f);
} else if(new_phase < last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundSource.BLOCKS, 0.2f, 0.7f);
}
// Block state
if((force_block_update_ || (state.getValue(FreezerBlock.PHASE) != new_phase))) {
state = state.setValue(FreezerBlock.PHASE, new_phase);
level.setBlock(worldPosition, state,3|16);
level.updateNeighborsAt(getBlockPos(), state.getBlock());
force_block_update_ = false;
}
if(dirty) setChanged();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,53 +1,54 @@
/*
* @file EdGlassBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class. Explicitly overrides some
* `Block` methods to return faster due to exclusive block properties.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.StainedGlassBlock;
import net.minecraft.item.DyeColor;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdGlassBlock extends StainedGlassBlock implements IDecorBlock
{
public EdGlassBlock(long config, AbstractBlock.Properties properties)
{ super(DyeColor.BLACK, properties); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState.getBlock()==this) ? true : super.skipRendering(state, adjacentBlockState, side); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
}
/*
* @file EdGlassBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class. Explicitly overrides some
* `Block` methods to return faster due to exclusive block properties.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.StainedGlassBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdGlassBlock extends StainedGlassBlock implements StandardBlocks.IStandardBlock
{
public EdGlassBlock(long config, BlockBehaviour.Properties properties)
{ super(DyeColor.BLACK, properties); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public StandardBlocks.IStandardBlock.RenderTypeHint getRenderTypeHint()
{ return StandardBlocks.IStandardBlock.RenderTypeHint.TRANSLUCENT; }
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState.getBlock()==this) || (super.skipRendering(state, adjacentBlockState, side)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
}

View file

@ -1,18 +0,0 @@
/*
* @file EdGroundBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for soils, floors, etc. Drawn out into a class
* to enable functionality block overrides.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
public class EdGroundBlock extends DecorBlock.Normal implements IDecorBlock
{
public EdGroundBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,134 +1,140 @@
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
public class EdHatchBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty OPEN = BlockStateProperties.OPEN;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
protected final ArrayList<VoxelShape> vshapes_open;
public EdHatchBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABBClosed, final AxisAlignedBB unrotatedAABBOpen)
{
super(config, builder, unrotatedAABBClosed); vshapes_open = makeHorizontalShapeLookup(new AxisAlignedBB[]{unrotatedAABBOpen});
registerDefaultState(super.defaultBlockState().setValue(OPEN, false).setValue(POWERED, false));
}
public EdHatchBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBsClosed, final AxisAlignedBB[] unrotatedAABBsOpen)
{ super(config, builder, unrotatedAABBsClosed); vshapes_open = makeHorizontalShapeLookup(unrotatedAABBsOpen); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return state.getValue(OPEN) ? vshapes_open.get((state.getValue(HORIZONTAL_FACING)).get3DDataValue() & 0x7) : super.getShape(state, source, pos, selectionContext); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return !state.getValue(OPEN); }
@Override
public boolean isLadder(BlockState state, IWorldReader world, BlockPos pos, LivingEntity entity)
{
if(!state.getValue(OPEN)) return false;
{
final BlockState up_state = world.getBlockState(pos.above());
if(up_state.is(this) && (up_state.getValue(OPEN))) return true;
if(up_state.isLadder(world, pos.above(), entity)) return true;
}
{
final BlockState down_state = world.getBlockState(pos.below());
if(down_state.is(this) && (down_state.getValue(OPEN))) return true;
if(down_state.isLadder(world, pos.below(), entity)) return true;
}
return false;
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(OPEN, POWERED); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open), 1|2);
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if((world.isClientSide) || (!(state.getBlock() instanceof EdHatchBlock))) return;
boolean powered = world.hasNeighborSignal(pos);
if(powered == state.getValue(POWERED)) return;
if(powered != state.getValue(OPEN)) world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
world.setBlock(pos, state.setValue(OPEN, powered).setValue(POWERED, powered), 1|2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if((!state.getValue(OPEN)) || (!(entity instanceof PlayerEntity))) return;
final PlayerEntity player = (PlayerEntity)entity;
if(entity.getLookAngle().y() > -0.75) return;
if(player.getDirection() != state.getValue(HORIZONTAL_FACING)) return;
Vector3d ppos = player.position();
Vector3d centre = Vector3d.atBottomCenterOf(pos);
Vector3d v = centre.subtract(ppos);
if(ppos.y() < (centre.y()-0.1) || (v.lengthSqr() > 0.3)) return;
v = v.scale(0.3);
player.push(v.x, 0, v.z);
}
}
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.ArrayList;
public class EdHatchBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final BooleanProperty OPEN = BlockStateProperties.OPEN;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
protected final ArrayList<VoxelShape> vshapes_open;
public EdHatchBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABBClosed, final AABB unrotatedAABBOpen)
{
super(config, builder, unrotatedAABBClosed); vshapes_open = makeHorizontalShapeLookup(new AABB[]{unrotatedAABBOpen});
registerDefaultState(super.defaultBlockState().setValue(OPEN, false).setValue(POWERED, false));
}
public EdHatchBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBsClosed, final AABB[] unrotatedAABBsOpen)
{ super(config, builder, unrotatedAABBsClosed); vshapes_open = makeHorizontalShapeLookup(unrotatedAABBsOpen); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return state.getValue(OPEN) ? vshapes_open.get((state.getValue(HORIZONTAL_FACING)).get3DDataValue() & 0x7) : super.getShape(state, source, pos, selectionContext); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return !state.getValue(OPEN); }
@Override
public boolean isLadder(BlockState state, LevelReader world, BlockPos pos, LivingEntity entity)
{
if(!state.getValue(OPEN)) return false;
{
final BlockState up_state = world.getBlockState(pos.above());
if(up_state.is(this) && (up_state.getValue(OPEN))) return true;
if(up_state.isLadder(world, pos.above(), entity)) return true;
}
{
final BlockState down_state = world.getBlockState(pos.below());
if(down_state.is(this) && (down_state.getValue(OPEN))) return true;
if(down_state.isLadder(world, pos.below(), entity)) return true;
}
return false;
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(OPEN, POWERED); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open), 1|2);
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if((world.isClientSide) || (!(state.getBlock() instanceof EdHatchBlock))) return;
boolean powered = world.hasNeighborSignal(pos);
if(powered == state.getValue(POWERED)) return;
if(powered != state.getValue(OPEN)) world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
world.setBlock(pos, state.setValue(OPEN, powered).setValue(POWERED, powered), 1|2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if((!state.getValue(OPEN)) || (!(entity instanceof final Player player))) return;
if(entity.getLookAngle().y() > -0.75) return;
if(player.getDirection() != state.getValue(HORIZONTAL_FACING)) return;
Vec3 ppos = player.position();
Vec3 centre = Vec3.atBottomCenterOf(pos);
Vec3 v = centre.subtract(ppos);
if(ppos.y() < (centre.y()-0.1) || (v.lengthSqr() > 0.3)) return;
v = v.scale(0.3);
player.push(v.x, 0, v.z);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,166 +1,172 @@
/*
* @file EdHorizontalSupportBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Horizontal ceiling support. Symmetric x axis, fixed in
* xz plane, therefore boolean placement state.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class EdHorizontalSupportBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public static final BooleanProperty EASTWEST = BooleanProperty.create("eastwest");
public static final BooleanProperty LEFTBEAM = BooleanProperty.create("leftbeam");
public static final BooleanProperty RIGHTBEAM = BooleanProperty.create("rightbeam");
public static final IntegerProperty DOWNCONNECT = IntegerProperty.create("downconnect", 0, 2);
protected final Map<BlockState, VoxelShape> AABBs;
public EdHorizontalSupportBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB mainBeamAABB, final AxisAlignedBB eastBeamAABB, final AxisAlignedBB thinDownBeamAABB, final AxisAlignedBB thickDownBeamAABB)
{
super(config|DecorBlock.CFG_HORIZIONTAL, builder);
Map<BlockState, VoxelShape> aabbs = new HashMap<>();
for(boolean eastwest:EASTWEST.getPossibleValues()) {
for(boolean leftbeam:LEFTBEAM.getPossibleValues()) {
for(boolean rightbeam:RIGHTBEAM.getPossibleValues()) {
for(int downconnect:DOWNCONNECT.getPossibleValues()) {
final BlockState state = defaultBlockState().setValue(EASTWEST, eastwest).setValue(LEFTBEAM, leftbeam).setValue(RIGHTBEAM, rightbeam).setValue(DOWNCONNECT, downconnect);
VoxelShape shape = VoxelShapes.create(Auxiliaries.getRotatedAABB(mainBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true));
if(rightbeam) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true)), IBooleanFunction.OR);
if(leftbeam) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.WEST:Direction.SOUTH, true)), IBooleanFunction.OR);
if(downconnect==1) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(thinDownBeamAABB), IBooleanFunction.OR);
if(downconnect==2) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(thickDownBeamAABB), IBooleanFunction.OR);
aabbs.put(state.setValue(WATERLOGGED, false), shape);
aabbs.put(state.setValue(WATERLOGGED, true), shape);
}
}
}
}
AABBs = aabbs;
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs.get(state); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EASTWEST, RIGHTBEAM, LEFTBEAM, DOWNCONNECT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return temp_block_update_until_better(super.getStateForPlacement(context).setValue(EASTWEST, context.getHorizontalDirection().getAxis()==Direction.Axis.X), context.getLevel(), context.getClickedPos()); }
private BlockState temp_block_update_until_better(BlockState state, IWorld world, BlockPos pos)
{
boolean ew = state.getValue(EASTWEST);
final BlockState rstate = world.getBlockState((!ew) ? (pos.east()) : (pos.south()) );
final BlockState lstate = world.getBlockState((!ew) ? (pos.west()) : (pos.north()) );
final BlockState dstate = world.getBlockState(pos.below());
int down_connector = 0;
if((dstate.getBlock() instanceof EdStraightPoleBlock)) {
final Direction dfacing = dstate.getValue(EdStraightPoleBlock.FACING);
final EdStraightPoleBlock pole = (EdStraightPoleBlock)dstate.getBlock();
if((dfacing.getAxis() == Direction.Axis.Y)) {
if((pole== ModContent.THICK_STEEL_POLE) || ((pole==ModContent.THICK_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 2;
} else if((pole==ModContent.THIN_STEEL_POLE) || ((pole==ModContent.THIN_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 1;
}
}
}
return state.setValue(RIGHTBEAM, (rstate.getBlock()==this) && (rstate.getValue(EASTWEST) != ew))
.setValue(LEFTBEAM , (lstate.getBlock()==this) && (lstate.getValue(EASTWEST) != ew))
.setValue(DOWNCONNECT , down_connector);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return temp_block_update_until_better(state, world, pos); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (held_stack.getItem() != this.asItem())) return ActionResultType.PASS;
if(!(hit.getDirection().getAxis().isVertical())) return ActionResultType.PASS;
final Direction placement_direction = player.getDirection();
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockItemUseContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return ActionResultType.sidedSuccess(world.isClientSide());
final BlockState new_state = getStateForPlacement(ctx);
if(new_state == null) return ActionResultType.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return ActionResultType.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return (rot==Rotation.CLOCKWISE_180) ? state : state.setValue(EASTWEST, !state.getValue(EASTWEST)); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
}
/*
* @file EdHorizontalSupportBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Horizontal ceiling support. Symmetric x axis, fixed in
* xz plane, therefore boolean placement state.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class EdHorizontalSupportBlock extends StandardBlocks.WaterLoggable
{
public static final BooleanProperty EASTWEST = BooleanProperty.create("eastwest");
public static final BooleanProperty LEFTBEAM = BooleanProperty.create("leftbeam");
public static final BooleanProperty RIGHTBEAM = BooleanProperty.create("rightbeam");
public static final IntegerProperty DOWNCONNECT = IntegerProperty.create("downconnect", 0, 2);
protected final Map<BlockState, VoxelShape> AABBs;
public EdHorizontalSupportBlock(long config, BlockBehaviour.Properties builder, final AABB mainBeamAABB, final AABB eastBeamAABB, final AABB thinDownBeamAABB, final AABB thickDownBeamAABB)
{
super(config|DecorBlock.CFG_HORIZIONTAL, builder);
Map<BlockState, VoxelShape> aabbs = new HashMap<>();
for(boolean eastwest:EASTWEST.getPossibleValues()) {
for(boolean leftbeam:LEFTBEAM.getPossibleValues()) {
for(boolean rightbeam:RIGHTBEAM.getPossibleValues()) {
for(int downconnect:DOWNCONNECT.getPossibleValues()) {
final BlockState state = defaultBlockState().setValue(EASTWEST, eastwest).setValue(LEFTBEAM, leftbeam).setValue(RIGHTBEAM, rightbeam).setValue(DOWNCONNECT, downconnect);
VoxelShape shape = Shapes.create(Auxiliaries.getRotatedAABB(mainBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true));
if(rightbeam) shape = Shapes.joinUnoptimized(shape, Shapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true)), BooleanOp.OR);
if(leftbeam) shape = Shapes.joinUnoptimized(shape, Shapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.WEST:Direction.SOUTH, true)), BooleanOp.OR);
if(downconnect==1) shape = Shapes.joinUnoptimized(shape, Shapes.create(thinDownBeamAABB), BooleanOp.OR);
if(downconnect==2) shape = Shapes.joinUnoptimized(shape, Shapes.create(thickDownBeamAABB), BooleanOp.OR);
aabbs.put(state.setValue(WATERLOGGED, false), shape);
aabbs.put(state.setValue(WATERLOGGED, true), shape);
}
}
}
}
AABBs = aabbs;
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs.get(state); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EASTWEST, RIGHTBEAM, LEFTBEAM, DOWNCONNECT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return temp_block_update_until_better(super.getStateForPlacement(context).setValue(EASTWEST, context.getHorizontalDirection().getAxis()==Direction.Axis.X), context.getLevel(), context.getClickedPos()); }
private BlockState temp_block_update_until_better(BlockState state, LevelAccessor world, BlockPos pos)
{
boolean ew = state.getValue(EASTWEST);
final BlockState rstate = world.getBlockState((!ew) ? (pos.east()) : (pos.south()) );
final BlockState lstate = world.getBlockState((!ew) ? (pos.west()) : (pos.north()) );
final BlockState dstate = world.getBlockState(pos.below());
int down_connector = 0;
if((dstate.getBlock() instanceof final EdStraightPoleBlock pole)) {
final Direction dfacing = dstate.getValue(EdStraightPoleBlock.FACING);
if((dfacing.getAxis() == Direction.Axis.Y)) {
if((pole== ModContent.THICK_STEEL_POLE) || ((pole==ModContent.THICK_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 2;
} else if((pole==ModContent.THIN_STEEL_POLE) || ((pole==ModContent.THIN_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 1;
}
}
}
return state.setValue(RIGHTBEAM, (rstate.getBlock()==this) && (rstate.getValue(EASTWEST) != ew))
.setValue(LEFTBEAM , (lstate.getBlock()==this) && (lstate.getValue(EASTWEST) != ew))
.setValue(DOWNCONNECT , down_connector);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return temp_block_update_until_better(state, world, pos); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (held_stack.getItem() != this.asItem())) return InteractionResult.PASS;
if(!(hit.getDirection().getAxis().isVertical())) return InteractionResult.PASS;
final Direction placement_direction = player.getDirection();
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockPlaceContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return InteractionResult.sidedSuccess(world.isClientSide());
final BlockState new_state = getStateForPlacement(ctx);
if(new_state == null) return InteractionResult.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return InteractionResult.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return (rot==Rotation.CLOCKWISE_180) ? state : state.setValue(EASTWEST, !state.getValue(EASTWEST)); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
}

File diff suppressed because it is too large Load diff

View file

@ -1,118 +1,121 @@
/*
* @file EdLadderBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Ladder block. The implementation is based on the vanilla
* net.minecraft.block.BlockLadder. Minor changes to enable
* later configuration (for block list based construction
* time configuration), does not drop when the block behind
* is broken, etc.
*/
package wile.engineersdecor.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.LivingEntity;
import net.minecraft.world.IWorldReader;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.vector.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
public class EdLadderBlock extends LadderBlock implements IDecorBlock
{
protected static final AxisAlignedBB EDLADDER_UNROTATED_AABB = Auxiliaries.getPixeledAABB(3, 0, 0, 13, 16, 3);
protected static final VoxelShape EDLADDER_SOUTH_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.SOUTH, false));
protected static final VoxelShape EDLADDER_EAST_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.EAST, false));
protected static final VoxelShape EDLADDER_WEST_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.WEST, false));
protected static final VoxelShape EDLADDER_NORTH_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.NORTH, false));
private static boolean without_speed_boost_ = false;
public static void on_config(boolean without_speed_boost)
{
without_speed_boost_ = without_speed_boost;
ModConfig.log("Config ladder: without-speed-boost:" + without_speed_boost_);
}
public EdLadderBlock(long config, AbstractBlock.Properties builder)
{ super(builder); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos)
{
switch ((Direction)state.getValue(FACING)) {
case NORTH: return EDLADDER_NORTH_AABB;
case SOUTH: return EDLADDER_SOUTH_AABB;
case WEST: return EDLADDER_WEST_AABB;
default: return EDLADDER_EAST_AABB;
}
}
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
@Override
public boolean isLadder(BlockState state, IWorldReader world, BlockPos pos, LivingEntity entity)
{ return true; }
// Player update event, forwarded from the main mod instance.
public static void onPlayerUpdateEvent(final PlayerEntity player)
{
if((without_speed_boost_) || (player.isOnGround()) || (!player.onClimbable()) || (player.isSteppingCarefully()) || (player.isSpectator())) return;
double lvy = player.getLookAngle().y;
if(Math.abs(lvy) < 0.92) return;
final BlockPos pos = player.blockPosition();
final BlockState state = player.level.getBlockState(pos);
if(!(state.getBlock() instanceof EdLadderBlock)) return;
player.fallDistance = 0;
if((player.getDeltaMovement().y() < 0) == (player.getLookAngle().y < 0)) {
player.makeStuckInBlock(state, new Vector3d(0.2, (lvy>0)?(3):(6), 0.2));
if(Math.abs(player.getDeltaMovement().y()) > 0.1) {
Vector3d vdiff = Vector3d.atBottomCenterOf(pos).subtract(player.position()).scale(1);
vdiff.add(Vector3d.atBottomCenterOf(state.getValue(FACING).getNormal()).scale(0.5));
vdiff = new Vector3d(vdiff.x, player.getDeltaMovement().y, vdiff.z);
player.setDeltaMovement(vdiff);
}
} else if(player.getLookAngle().y > 0) {
player.makeStuckInBlock(state, new Vector3d(1, 0.05, 1));
}
}
}
/*
* @file EdLadderBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Ladder block. The implementation is based on the vanilla
* net.minecraft.block.BlockLadder. Minor changes to enable
* later configuration (for block list based construction
* time configuration), does not drop when the block behind
* is broken, etc.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.LadderBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdLadderBlock extends LadderBlock implements StandardBlocks.IStandardBlock
{
protected static final AABB EDLADDER_UNROTATED_AABB = Auxiliaries.getPixeledAABB(3, 0, 0, 13, 16, 3);
protected static final VoxelShape EDLADDER_SOUTH_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.SOUTH, false));
protected static final VoxelShape EDLADDER_EAST_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.EAST, false));
protected static final VoxelShape EDLADDER_WEST_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.WEST, false));
protected static final VoxelShape EDLADDER_NORTH_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.NORTH, false));
private static boolean without_speed_boost_ = false;
public static void on_config(boolean without_speed_boost)
{
without_speed_boost_ = without_speed_boost;
ModConfig.log("Config ladder: without-speed-boost:" + without_speed_boost_);
}
public EdLadderBlock(long config, BlockBehaviour.Properties builder)
{ super(builder); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos)
{
return switch(state.getValue(FACING)) {
case NORTH -> EDLADDER_NORTH_AABB;
case SOUTH -> EDLADDER_SOUTH_AABB;
case WEST -> EDLADDER_WEST_AABB;
default -> EDLADDER_EAST_AABB;
};
}
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
@Override
public boolean isLadder(BlockState state, LevelReader world, BlockPos pos, LivingEntity entity)
{ return true; }
// Player update event, forwarded from the main mod instance.
public static void onPlayerUpdateEvent(final Player player)
{
if((without_speed_boost_) || (player.isOnGround()) || (!player.onClimbable()) || (player.isSteppingCarefully()) || (player.isSpectator())) return;
double lvy = player.getLookAngle().y;
if(Math.abs(lvy) < 0.92) return;
final BlockPos pos = player.blockPosition();
final BlockState state = player.level.getBlockState(pos);
if(!(state.getBlock() instanceof EdLadderBlock)) return;
player.fallDistance = 0;
if((player.getDeltaMovement().y() < 0) == (player.getLookAngle().y < 0)) {
player.makeStuckInBlock(state, new Vec3(0.2, (lvy>0)?(3):(6), 0.2));
if(Math.abs(player.getDeltaMovement().y()) > 0.1) {
Vec3 vdiff = Vec3.atBottomCenterOf(pos).subtract(player.position()).scale(1);
vdiff.add(Vec3.atBottomCenterOf(state.getValue(FACING).getNormal()).scale(0.5));
vdiff = new Vec3(vdiff.x, player.getDeltaMovement().y, vdiff.z);
player.setDeltaMovement(vdiff);
}
} else if(player.getLookAngle().y > 0) {
player.makeStuckInBlock(state, new Vec3(1, 0.05, 1));
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,278 +1,273 @@
/*
* @file EdPipeValve.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basically a piece of pipe that does not connect to
* pipes on the side, and conducts fluids only in one way.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.Direction;
import net.minecraft.util.Rotation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class EdPipeValve
{
public static final int CFG_CHECK_VALVE = 0x1;
public static final int CFG_ANALOG_VALVE = 0x2;
public static final int CFG_REDSTONE_CONTROLLED_VALVE = 0x4;
public static void on_config(int container_size_decl, int redstone_slope)
{
PipeValveTileEntity.fluid_maxflow_mb = MathHelper.clamp(container_size_decl, 1, 10000);
PipeValveTileEntity.redstone_flow_slope_mb = MathHelper.clamp(redstone_slope, 1, 10000);
ModConfig.log("Config pipe valve: maxflow:" + PipeValveTileEntity.fluid_maxflow_mb + "mb, redstone amp:" + PipeValveTileEntity.redstone_flow_slope_mb + "mb/sig.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
public static final BooleanProperty RS_CN_N = BooleanProperty.create("rs_n");
public static final BooleanProperty RS_CN_S = BooleanProperty.create("rs_s");
public static final BooleanProperty RS_CN_E = BooleanProperty.create("rs_e");
public static final BooleanProperty RS_CN_W = BooleanProperty.create("rs_w");
public static final BooleanProperty RS_CN_U = BooleanProperty.create("rs_u");
public static final BooleanProperty RS_CN_D = BooleanProperty.create("rs_d");
public final int valve_config;
public PipeValveBlock(long config, int valve_config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); this.valve_config = valve_config; }
private BlockState get_rsconnector_state(BlockState state, IWorld world, BlockPos pos, @Nullable BlockPos fromPos)
{
if((valve_config & (CFG_REDSTONE_CONTROLLED_VALVE))==0) return state;
Direction.Axis bfa = state.getValue(FACING).getAxis();
for(Direction f:Direction.values()) {
boolean cn = (f.getAxis() != bfa);
if(cn) {
BlockPos nbp = pos.relative(f);
if((fromPos != null) && (!nbp.equals(fromPos))) continue; // do not change connectors except form the frompos.
BlockState nbs = world.getBlockState(nbp);
if((nbs.getBlock() instanceof PipeValveBlock) || ((!nbs.isSignalSource()) && (!nbs.canConnectRedstone(world, nbp, f.getOpposite())))) cn = false;
}
switch(f) {
case NORTH: state = state.setValue(RS_CN_N, cn); break;
case SOUTH: state = state.setValue(RS_CN_S, cn); break;
case EAST: state = state.setValue(RS_CN_E, cn); break;
case WEST: state = state.setValue(RS_CN_W, cn); break;
case UP: state = state.setValue(RS_CN_U, cn); break;
case DOWN: state = state.setValue(RS_CN_D, cn); break;
}
}
return state;
}
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return VoxelShapes.block(); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RS_CN_N, RS_CN_S, RS_CN_E, RS_CN_W, RS_CN_U, RS_CN_D); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
return super.getStateForPlacement(context).setValue(RS_CN_N, false).setValue(RS_CN_S, false).setValue(RS_CN_E, false)
.setValue(RS_CN_W, false).setValue(RS_CN_U, false).setValue(RS_CN_D, false);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return get_rsconnector_state(state, world, pos, null); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{ world.updateNeighborsAt(pos,this); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new PipeValveTileEntity(); }
@Override
public BlockState rotate(BlockState state, IWorld world, BlockPos pos, Rotation direction)
{ return get_rsconnector_state(state, world, pos, null); } // don't rotate at all
@Override
public boolean canConnectRedstone(BlockState state, IBlockReader world, BlockPos pos, @Nullable Direction side)
{ return (side!=null) && (side!=state.getValue(FACING)) && (side!=state.getValue(FACING).getOpposite()); }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveTileEntity extends TileEntity implements ICapabilityProvider //, IFluidPipe
{
protected static int fluid_maxflow_mb = 1000;
protected static int redstone_flow_slope_mb = 1000/15;
private Direction block_facing_ = null;
private boolean filling_ = false;
private int valve_config_;
public PipeValveTileEntity()
{ this(ModContent.TET_STRAIGHT_PIPE_VALVE); }
public PipeValveTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
private Direction block_facing()
{
BlockState st = getLevel().getBlockState(getBlockPos());
return (st.getBlock() instanceof PipeValveBlock) ? st.getValue(PipeValveBlock.FACING) : Direction.NORTH;
}
private long valve_config()
{
if(valve_config_ <= 0) {
final Block block = getLevel().getBlockState(getBlockPos()).getBlock();
if(block instanceof PipeValveBlock) valve_config_ = ((PipeValveBlock)block).valve_config;
}
return valve_config_;
}
// TileEntity -----------------------------------------------------------------------------
@Override
public void setRemoved()
{
super.setRemoved();
back_flow_handler_.invalidate();
fluid_handler_.invalidate();
}
// ICapabilityProvider --------------------------------------------------------------------
private final LazyOptional<IFluidHandler> back_flow_handler_ = LazyOptional.of(() -> (IFluidHandler)new BackFlowHandler());
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> (IFluidHandler)new MainFlowHandler(this));
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
Direction bf = block_facing();
if(facing == bf) return back_flow_handler_.cast();
if(facing == bf.getOpposite()) return fluid_handler_.cast();
return LazyOptional.empty();
}
return super.getCapability(capability, facing);
}
// IFluidHandlers
@Nullable
private IFluidHandler forward_fluid_handler()
{
final TileEntity te = level.getBlockEntity(worldPosition.relative(block_facing()));
if(te == null) return null;
return te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, block_facing().getOpposite()).orElse(null);
}
// Forward flow handler --
private static class MainFlowHandler implements IFluidHandler
{
private final PipeValveTileEntity te;
public MainFlowHandler(PipeValveTileEntity te) { this.te = te; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return fluid_maxflow_mb; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
@Override public int fill(FluidStack resource, FluidAction action)
{
if(te.filling_) return 0;
final IFluidHandler fh = te.forward_fluid_handler();
if(fh==null) return 0;
FluidStack res = resource.copy();
if((te.valve_config() & CFG_REDSTONE_CONTROLLED_VALVE) != 0) {
int rs = te.level.getBestNeighborSignal(te.worldPosition);
if(rs <= 0) return 0;
if(((te.valve_config() & CFG_ANALOG_VALVE) != 0) && (rs < 15)) res.setAmount(MathHelper.clamp(rs * redstone_flow_slope_mb, 1, res.getAmount()));
}
if(res.getAmount() > fluid_maxflow_mb) res.setAmount(fluid_maxflow_mb);
te.filling_ = true;
int n_filled = fh.fill(res, action);
te.filling_ = false;
return n_filled;
}
}
// Back flow prevention handler --
private static class BackFlowHandler implements IFluidHandler
{
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return 0; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return false; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
}
}
}
/*
* @file EdPipeValve.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basically a piece of pipe that does not connect to
* pipes on the side, and conducts fluids only in one way.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.RsSignals;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class EdPipeValve
{
public static final int CFG_CHECK_VALVE = 0x1;
public static final int CFG_ANALOG_VALVE = 0x2;
public static final int CFG_REDSTONE_CONTROLLED_VALVE = 0x4;
public static void on_config(int container_size_decl, int redstone_slope)
{
PipeValveTileEntity.fluid_maxflow_mb = Mth.clamp(container_size_decl, 1, 10000);
PipeValveTileEntity.redstone_flow_slope_mb = Mth.clamp(redstone_slope, 1, 10000);
ModConfig.log("Config pipe valve: maxflow:" + PipeValveTileEntity.fluid_maxflow_mb + "mb, redstone amp:" + PipeValveTileEntity.redstone_flow_slope_mb + "mb/sig.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveBlock extends StandardBlocks.DirectedWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<PipeValveTileEntity>
{
public static final BooleanProperty RS_CN_N = BooleanProperty.create("rs_n");
public static final BooleanProperty RS_CN_S = BooleanProperty.create("rs_s");
public static final BooleanProperty RS_CN_E = BooleanProperty.create("rs_e");
public static final BooleanProperty RS_CN_W = BooleanProperty.create("rs_w");
public static final BooleanProperty RS_CN_U = BooleanProperty.create("rs_u");
public static final BooleanProperty RS_CN_D = BooleanProperty.create("rs_d");
public final int valve_config;
public PipeValveBlock(long config, int valve_config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); this.valve_config = valve_config; }
@Override
@Nullable
public BlockEntityType<EdPipeValve.PipeValveTileEntity> getBlockEntityType()
{ return ModContent.TET_STRAIGHT_PIPE_VALVE; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return Shapes.block(); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RS_CN_N, RS_CN_S, RS_CN_E, RS_CN_W, RS_CN_U, RS_CN_D); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
return super.getStateForPlacement(context).setValue(RS_CN_N, false).setValue(RS_CN_S, false).setValue(RS_CN_E, false)
.setValue(RS_CN_W, false).setValue(RS_CN_U, false).setValue(RS_CN_D, false);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return get_rsconnector_state(state, world, pos, null); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{ world.updateNeighborsAt(pos,this); }
@Override
public BlockState rotate(BlockState state, LevelAccessor world, BlockPos pos, Rotation direction)
{ return get_rsconnector_state(state, world, pos, null); } // don't rotate at all
@Override
public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side)
{ return (side!=null) && (side!=state.getValue(FACING)) && (side!=state.getValue(FACING).getOpposite()); }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation") // public boolean canConnectRedstone(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { return true; }
public boolean isSignalSource(BlockState p_60571_)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
private BlockState get_rsconnector_state(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockPos fromPos)
{
if((valve_config & (CFG_REDSTONE_CONTROLLED_VALVE))==0) return state;
Direction.Axis bfa = state.getValue(FACING).getAxis();
for(Direction f:Direction.values()) {
boolean cn = (f.getAxis() != bfa);
if(cn) {
BlockPos nbp = pos.relative(f);
if((fromPos != null) && (!nbp.equals(fromPos))) continue; // do not change connectors except form the frompos.
BlockState nbs = world.getBlockState(nbp);
if((nbs.getBlock() instanceof PipeValveBlock) || (!nbs.isSignalSource()) && (RsSignals.hasSignalConnector(nbs, world, nbp, f.getOpposite()))) cn = false;
}
switch (f) {
case NORTH -> state = state.setValue(RS_CN_N, cn);
case SOUTH -> state = state.setValue(RS_CN_S, cn);
case EAST -> state = state.setValue(RS_CN_E, cn);
case WEST -> state = state.setValue(RS_CN_W, cn);
case UP -> state = state.setValue(RS_CN_U, cn);
case DOWN -> state = state.setValue(RS_CN_D, cn);
}
}
return state;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
protected static int fluid_maxflow_mb = 1000;
protected static int redstone_flow_slope_mb = 1000/15;
private final Direction block_facing_ = null;
private boolean filling_ = false;
private int valve_config_;
public PipeValveTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_STRAIGHT_PIPE_VALVE, pos, state); }
private Direction block_facing()
{
BlockState st = getLevel().getBlockState(getBlockPos());
return (st.getBlock() instanceof PipeValveBlock) ? st.getValue(PipeValveBlock.FACING) : Direction.NORTH;
}
private long valve_config()
{
if(valve_config_ <= 0) {
final Block block = getLevel().getBlockState(getBlockPos()).getBlock();
if(block instanceof PipeValveBlock) valve_config_ = ((PipeValveBlock)block).valve_config;
}
return valve_config_;
}
// BlockEntity -----------------------------------------------------------------------------
@Override
public void setRemoved()
{
super.setRemoved();
back_flow_handler_.invalidate();
fluid_handler_.invalidate();
}
// ICapabilityProvider --------------------------------------------------------------------
private final LazyOptional<IFluidHandler> back_flow_handler_ = LazyOptional.of(BackFlowHandler::new);
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new MainFlowHandler(this));
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
Direction bf = block_facing();
if(facing == bf) return back_flow_handler_.cast();
if(facing == bf.getOpposite()) return fluid_handler_.cast();
return LazyOptional.empty();
}
return super.getCapability(capability, facing);
}
// IFluidHandlers
@Nullable
private IFluidHandler forward_fluid_handler()
{
final BlockEntity te = level.getBlockEntity(worldPosition.relative(block_facing()));
if(te == null) return null;
return te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, block_facing().getOpposite()).orElse(null);
}
// Forward flow handler --
private static class MainFlowHandler implements IFluidHandler
{
private final PipeValveTileEntity te;
public MainFlowHandler(PipeValveTileEntity te) { this.te = te; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return fluid_maxflow_mb; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
@Override public int fill(FluidStack resource, FluidAction action)
{
if(te.filling_) return 0;
final IFluidHandler fh = te.forward_fluid_handler();
if(fh==null) return 0;
FluidStack res = resource.copy();
if((te.valve_config() & CFG_REDSTONE_CONTROLLED_VALVE) != 0) {
int rs = te.level.getBestNeighborSignal(te.worldPosition);
if(rs <= 0) return 0;
if(((te.valve_config() & CFG_ANALOG_VALVE) != 0) && (rs < 15)) res.setAmount(Mth.clamp(rs * redstone_flow_slope_mb, 1, res.getAmount()));
}
if(res.getAmount() > fluid_maxflow_mb) res.setAmount(fluid_maxflow_mb);
te.filling_ = true;
int n_filled = fh.fill(res, action);
te.filling_ = false;
return n_filled;
}
}
// Back flow prevention handler --
private static class BackFlowHandler implements IFluidHandler
{
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return 0; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return false; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,96 +1,100 @@
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdRailingBlock extends DecorBlock.HorizontalFourWayWaterLoggable implements IDecorBlock
{
public EdRailingBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB base_aabb, final AxisAlignedBB railing_aabb)
{ super(config, properties, base_aabb, railing_aabb, 0); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext useContext)
{ return (useContext.getItemInHand().getItem() == asItem()) ? true : super.canBeReplaced(state, useContext); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
if(context.getClickedFace() != Direction.UP) return null;
BlockState state = context.getLevel().getBlockState(context.getClickedPos());
if(state.getBlock() != this) state = super.getStateForPlacement(context);
final Vector3d rhv = context.getClickLocation().subtract(Vector3d.atCenterOf(context.getClickedPos()));
BooleanProperty side = getDirectionProperty(Direction.getNearest(rhv.x, 0, rhv.z));
return state.setValue(side, true);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return ActionResultType.PASS;
Direction face = hit.getDirection();
if(!face.getAxis().isHorizontal()) return ActionResultType.sidedSuccess(world.isClientSide());
final Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos()));
if(rhv.multiply(Vector3d.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
state = state.setValue(railing, add);
if((!state.getValue(NORTH)) && (!state.getValue(EAST)) && (!state.getValue(SOUTH)) && (!state.getValue(WEST))) {
state = (world.getFluidState(pos).getType() == Fluids.WATER) ? Blocks.WATER.defaultBlockState() : (Blocks.AIR.defaultBlockState());
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
} else {
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
drops.add(new ItemStack(state.getBlock().asItem(), Math.max(n, 1)));
return drops;
}
}
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdRailingBlock extends StandardBlocks.HorizontalFourWayWaterLoggable
{
public EdRailingBlock(long config, BlockBehaviour.Properties properties, final AABB base_aabb, final AABB railing_aabb)
{ super(config, properties, base_aabb, railing_aabb, 0); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext useContext)
{ return (useContext.getItemInHand().getItem() == asItem()) || super.canBeReplaced(state, useContext); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
if(context.getClickedFace() != Direction.UP) return null;
BlockState state = context.getLevel().getBlockState(context.getClickedPos());
if(state.getBlock() != this) state = super.getStateForPlacement(context);
final Vec3 rhv = context.getClickLocation().subtract(Vec3.atCenterOf(context.getClickedPos()));
BooleanProperty side = getDirectionProperty(Direction.getNearest(rhv.x, 0, rhv.z));
return state.setValue(side, true);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return InteractionResult.PASS;
Direction face = hit.getDirection();
if(!face.getAxis().isHorizontal()) return InteractionResult.sidedSuccess(world.isClientSide());
final Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos()));
if(rhv.multiply(Vec3.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
state = state.setValue(railing, add);
if((!state.getValue(NORTH)) && (!state.getValue(EAST)) && (!state.getValue(SOUTH)) && (!state.getValue(WEST))) {
state = (world.getFluidState(pos).getType() == Fluids.WATER) ? Blocks.WATER.defaultBlockState() : (Blocks.AIR.defaultBlockState());
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
} else {
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
drops.add(new ItemStack(state.getBlock().asItem(), Math.max(n, 1)));
return drops;
}
}

View file

@ -1,231 +1,226 @@
/*
* @file EdRoofBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.fluid.FluidState;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.Half;
import net.minecraft.state.properties.StairsShape;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
public class EdRoofBlock extends StandardBlocks.HorizontalWaterLoggable implements IDecorBlock
{
public static final EnumProperty<StairsShape> SHAPE = BlockStateProperties.STAIRS_SHAPE;
public static final EnumProperty<Half> HALF = BlockStateProperties.HALF;
private final VoxelShape[][][] shape_cache_;
public EdRoofBlock(long config, AbstractBlock.Properties properties)
{ this(config, properties.dynamicShape(), VoxelShapes.empty(), VoxelShapes.empty()); }
public EdRoofBlock(long config, AbstractBlock.Properties properties, VoxelShape add, VoxelShape cut)
{
super(config, properties, Auxiliaries.getPixeledAABB(0, 0,0,16, 8, 16));
registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH).setValue(SHAPE, StairsShape.STRAIGHT));
shape_cache_ = makeShapes(add, cut);
}
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return false; }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public float getShadeBrightness(BlockState state, IBlockReader world, BlockPos pos)
{ return 0.98f; }
@Override
@SuppressWarnings("deprecation")
public int getLightBlock(BlockState state, IBlockReader world, BlockPos pos)
{ return 1; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext context)
{ return shape_cache_[state.getValue(HALF).ordinal()][state.getValue(HORIZONTAL_FACING).get3DDataValue()][state.getValue(SHAPE).ordinal()]; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SHAPE, HALF); }
@Override
public FluidState getFluidState(BlockState state)
{ return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); }
@Override
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return false; }
@Override
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockPos pos = context.getClickedPos();
Direction face = context.getClickedFace();
BlockState state = defaultBlockState()
.setValue(HORIZONTAL_FACING, context.getHorizontalDirection())
.setValue(HALF, (face == Direction.DOWN) ? Half.TOP : Half.BOTTOM)
.setValue(WATERLOGGED, context.getLevel().getFluidState(pos).getType()==Fluids.WATER);
return state.setValue(SHAPE, getStairsShapeProperty(state, context.getLevel(), pos));
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
return (facing.getAxis().isHorizontal()) ? (state.setValue(SHAPE, getStairsShapeProperty(state, world, pos))) : (super.updateShape(state, facing, facingState, world, pos, facingPos));
}
@Override
public BlockState rotate(BlockState state, Rotation rot)
{ return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror where)
{
if((where==Mirror.LEFT_RIGHT) && (state.getValue(HORIZONTAL_FACING).getAxis()==Direction.Axis.Z)) {
switch(state.getValue(SHAPE)) {
case INNER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case INNER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case OUTER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
default: return state.rotate(Rotation.CLOCKWISE_180);
}
} else if((where==Mirror.FRONT_BACK) && (state.getValue(HORIZONTAL_FACING).getAxis() == Direction.Axis.X)) {
switch(state.getValue(SHAPE)) {
case INNER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case INNER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case OUTER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
case STRAIGHT: return state.rotate(Rotation.CLOCKWISE_180);
}
}
return super.mirror(state, where);
}
private static boolean isRoofBlock(BlockState state)
{ return (state.getBlock() instanceof EdRoofBlock); }
private static boolean isOtherRoofState(BlockState state, IBlockReader world, BlockPos pos, Direction facing)
{
BlockState st = world.getBlockState(pos.relative(facing));
return (!isRoofBlock(st)) || (st.getValue(HORIZONTAL_FACING) != state.getValue(HORIZONTAL_FACING));
}
private static VoxelShape[][][] makeShapes(VoxelShape add, VoxelShape cut)
{
VoxelShape[][][] shapes = new VoxelShape[2][6][5];
for(int half_index=0; half_index<Half.values().length; ++half_index) {
for(int direction_index=0; direction_index<Direction.values().length; ++direction_index) {
for(int stairs_shape_index=0; stairs_shape_index<StairsShape.values().length; ++stairs_shape_index) {
VoxelShape shape = makeShape(half_index, direction_index, stairs_shape_index);
try {
// Only in case something changes and this fails, log but do not prevent the game from starting.
// Roof shapes are not the most important thing in the world.
if(!add.isEmpty()) shape = VoxelShapes.joinUnoptimized(shape, add, IBooleanFunction.OR);
if(!cut.isEmpty()) shape = VoxelShapes.joinUnoptimized(shape, cut, IBooleanFunction.ONLY_FIRST);
} catch(Throwable ex) {
Auxiliaries.logError("Failed to cut shape using Boolean function. This is bug.");
}
shapes[half_index][direction_index][stairs_shape_index] = shape;
}
}
}
return shapes;
}
private static VoxelShape makeShape(int half_index, int direction_index, int stairs_shape_index)
{
AxisAlignedBB[] straight = new AxisAlignedBB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 0, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 0, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 0, 16, 16, 16)
};
AxisAlignedBB[] pyramid = new AxisAlignedBB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 4, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 8, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 12, 16, 16, 16)
};
final Half half = Half.values()[half_index];
if(half==Half.TOP) {
straight = Auxiliaries.getMirroredAABB(straight, Axis.Y);
pyramid = Auxiliaries.getMirroredAABB(pyramid, Axis.Y);
}
Direction direction = Direction.from3DDataValue(direction_index);
if((direction==Direction.UP) || (direction==Direction.DOWN)) return VoxelShapes.block();
direction_index = (direction.get2DDataValue()+1) & 0x03; // ref NORTH -> EAST for stairs compliancy.
final StairsShape stairs = StairsShape.values()[stairs_shape_index];
switch(stairs) {
case STRAIGHT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(straight, direction_index));
case OUTER_LEFT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index-1));
case OUTER_RIGHT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index));
case INNER_LEFT:
return Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index-1)
);
case INNER_RIGHT:
return Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index+1)
);
default:
return VoxelShapes.block();
}
}
private static StairsShape getStairsShapeProperty(BlockState state, IBlockReader world, BlockPos pos)
{
Direction direction = state.getValue(HORIZONTAL_FACING);
{
BlockState ns = world.getBlockState(pos.relative(direction));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf.getOpposite())) {
return (nf == direction.getCounterClockWise()) ? StairsShape.OUTER_LEFT : StairsShape.OUTER_RIGHT;
}
}
}
{
BlockState ns = world.getBlockState(pos.relative(direction.getOpposite()));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf)) {
return (nf == direction.getCounterClockWise()) ? StairsShape.INNER_LEFT : StairsShape.INNER_RIGHT;
}
}
}
return StairsShape.STRAIGHT;
}
}
/*
* @file EdRoofBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
public class EdRoofBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final EnumProperty<StairsShape> SHAPE = BlockStateProperties.STAIRS_SHAPE;
public static final EnumProperty<Half> HALF = BlockStateProperties.HALF;
private final VoxelShape[][][] shape_cache_;
public EdRoofBlock(long config, BlockBehaviour.Properties properties)
{ this(config, properties.dynamicShape(), Shapes.empty(), Shapes.empty()); }
public EdRoofBlock(long config, BlockBehaviour.Properties properties, VoxelShape add, VoxelShape cut)
{
super(config, properties, Auxiliaries.getPixeledAABB(0, 0,0,16, 8, 16));
registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH).setValue(SHAPE, StairsShape.STRAIGHT));
shape_cache_ = makeShapes(add, cut);
}
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return false; }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos)
{ return 0.98f; }
@Override
@SuppressWarnings("deprecation")
public int getLightBlock(BlockState state, BlockGetter world, BlockPos pos)
{ return 1; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context)
{ return shape_cache_[state.getValue(HALF).ordinal()][state.getValue(HORIZONTAL_FACING).get3DDataValue()][state.getValue(SHAPE).ordinal()]; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SHAPE, HALF); }
@Override
public FluidState getFluidState(BlockState state)
{ return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); }
@Override
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return false; }
@Override
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockPos pos = context.getClickedPos();
Direction face = context.getClickedFace();
BlockState state = defaultBlockState()
.setValue(HORIZONTAL_FACING, context.getHorizontalDirection())
.setValue(HALF, (face == Direction.DOWN) ? Half.TOP : Half.BOTTOM)
.setValue(WATERLOGGED, context.getLevel().getFluidState(pos).getType()==Fluids.WATER);
return state.setValue(SHAPE, getStairsShapeProperty(state, context.getLevel(), pos));
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
return (facing.getAxis().isHorizontal()) ? (state.setValue(SHAPE, getStairsShapeProperty(state, world, pos))) : (super.updateShape(state, facing, facingState, world, pos, facingPos));
}
@Override
public BlockState rotate(BlockState state, Rotation rot)
{ return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror where)
{
if((where==Mirror.LEFT_RIGHT) && (state.getValue(HORIZONTAL_FACING).getAxis()==Direction.Axis.Z)) {
return switch (state.getValue(SHAPE)) {
case INNER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case INNER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case OUTER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
default -> state.rotate(Rotation.CLOCKWISE_180);
};
} else if((where==Mirror.FRONT_BACK) && (state.getValue(HORIZONTAL_FACING).getAxis() == Direction.Axis.X)) {
return switch (state.getValue(SHAPE)) {
case INNER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case INNER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case OUTER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
case STRAIGHT -> state.rotate(Rotation.CLOCKWISE_180);
};
}
return super.mirror(state, where);
}
private static boolean isRoofBlock(BlockState state)
{ return (state.getBlock() instanceof EdRoofBlock); }
private static boolean isOtherRoofState(BlockState state, BlockGetter world, BlockPos pos, Direction facing)
{
BlockState st = world.getBlockState(pos.relative(facing));
return (!isRoofBlock(st)) || (st.getValue(HORIZONTAL_FACING) != state.getValue(HORIZONTAL_FACING));
}
private static VoxelShape[][][] makeShapes(VoxelShape add, VoxelShape cut)
{
VoxelShape[][][] shapes = new VoxelShape[2][6][5];
for(int half_index=0; half_index<Half.values().length; ++half_index) {
for(int direction_index=0; direction_index<Direction.values().length; ++direction_index) {
for(int stairs_shape_index=0; stairs_shape_index<StairsShape.values().length; ++stairs_shape_index) {
VoxelShape shape = makeShape(half_index, direction_index, stairs_shape_index);
try {
// Only in case something changes and this fails, log but do not prevent the game from starting.
// Roof shapes are not the most important thing in the world.
if(!add.isEmpty()) shape = Shapes.joinUnoptimized(shape, add, BooleanOp.OR);
if(!cut.isEmpty()) shape = Shapes.joinUnoptimized(shape, cut, BooleanOp.ONLY_FIRST);
} catch(Throwable ex) {
Auxiliaries.logError("Failed to cut shape using Boolean function. This is bug.");
}
shapes[half_index][direction_index][stairs_shape_index] = shape;
}
}
}
return shapes;
}
private static VoxelShape makeShape(int half_index, int direction_index, int stairs_shape_index)
{
AABB[] straight = new AABB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 0, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 0, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 0, 16, 16, 16)
};
AABB[] pyramid = new AABB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 4, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 8, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 12, 16, 16, 16)
};
final Half half = Half.values()[half_index];
if(half==Half.TOP) {
straight = Auxiliaries.getMirroredAABB(straight, Direction.Axis.Y);
pyramid = Auxiliaries.getMirroredAABB(pyramid, Direction.Axis.Y);
}
Direction direction = Direction.from3DDataValue(direction_index);
if((direction==Direction.UP) || (direction==Direction.DOWN)) return Shapes.block();
direction_index = (direction.get2DDataValue()+1) & 0x03; // ref NORTH -> EAST for stairs compliancy.
final StairsShape stairs = StairsShape.values()[stairs_shape_index];
return switch (stairs) {
case STRAIGHT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(straight, direction_index));
case OUTER_LEFT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index - 1));
case OUTER_RIGHT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index));
case INNER_LEFT -> Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index - 1)
);
case INNER_RIGHT -> Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index + 1)
);
};
}
private static StairsShape getStairsShapeProperty(BlockState state, BlockGetter world, BlockPos pos)
{
Direction direction = state.getValue(HORIZONTAL_FACING);
{
BlockState ns = world.getBlockState(pos.relative(direction));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf.getOpposite())) {
return (nf == direction.getCounterClockWise()) ? StairsShape.OUTER_LEFT : StairsShape.OUTER_RIGHT;
}
}
}
{
BlockState ns = world.getBlockState(pos.relative(direction.getOpposite()));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf)) {
return (nf == direction.getCounterClockWise()) ? StairsShape.INNER_LEFT : StairsShape.INNER_RIGHT;
}
}
}
return StairsShape.STRAIGHT;
}
}

View file

@ -1,18 +0,0 @@
/*
* @file EdSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.VariantSlabBlock;
import net.minecraft.block.*;
public class EdSlabBlock extends VariantSlabBlock implements IDecorBlock
{
public EdSlabBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,19 +0,0 @@
/*
* @file EdSlabSliceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.SlabSliceBlock;
import net.minecraft.block.AbstractBlock;
public class EdSlabSliceBlock extends SlabSliceBlock implements IDecorBlock
{
public EdSlabSliceBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,266 +1,229 @@
/*
* @file EdSolarPanel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.LightType;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.AbstractBlock;
public class EdSolarPanel
{
public static void on_config(int peak_power_per_tick)
{ SolarPanelTileEntity.on_config(peak_power_per_tick); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final IntegerProperty EXPOSITION = IntegerProperty.create("exposition", 0, 4);
public SolarPanelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(EXPOSITION, 1));
}
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EXPOSITION); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdSolarPanel.SolarPanelTileEntity(); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof SolarPanelTileEntity) ((SolarPanelTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider, IEnergyStorage
{
public static final int DEFAULT_PEAK_POWER = 40;
public static final int TICK_INTERVAL = 4;
public static final int ACCUMULATION_INTERVAL = 8;
private static final Direction transfer_directions_[] = {Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH };
private static int peak_power_per_tick_ = DEFAULT_PEAK_POWER;
private static final int max_power_storage_ = 64000;
private static final int max_feed_power = 8192;
private static int feeding_threshold = max_power_storage_/5;
private static int balancing_threshold = max_power_storage_/10;
private int tick_timer_ = 0;
private int recalc_timer_ = 0;
private int accumulated_power_ = 0;
private int current_production_ = 0;
private int current_feedin_ = 0;
private boolean output_enabled_ = false;
public static void on_config(int peak_power_per_tick)
{
peak_power_per_tick_ = MathHelper.clamp(peak_power_per_tick, 2, 8192);
feeding_threshold = Math.max(max_power_storage_/5, 1000);
balancing_threshold = Math.max(max_power_storage_/10, 1000);
ModConfig.log("Config small solar panel: Peak production:" + peak_power_per_tick_ + "/t.");
}
//------------------------------------------------------------------------------------------------------------------
public SolarPanelTileEntity()
{ this(ModContent.TET_SMALL_SOLAR_PANEL); }
public SolarPanelTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public void readnbt(CompoundNBT nbt, boolean update_packet)
{ accumulated_power_ = nbt.getInt("energy"); }
protected void writenbt(CompoundNBT nbt, boolean update_packet)
{ nbt.putInt("energy", accumulated_power_); }
public void state_message(PlayerEntity player)
{
String soc = Integer.toString(MathHelper.clamp((accumulated_power_*100/max_power_storage_),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_solar_panel.status", new Object[]{soc, max_power_storage_, current_production_, current_feedin_ }));
}
// IEnergyStorage --------------------------------------------------------------------------
@Override
public boolean canExtract()
{ return true; }
@Override
public boolean canReceive()
{ return false; }
@Override
public int getMaxEnergyStored()
{ return max_power_storage_; }
@Override
public int getEnergyStored()
{ return accumulated_power_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{
int p = Math.min(accumulated_power_, maxExtract);
if(!simulate) accumulated_power_ -= p;
return p;
}
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{ return 0; }
// ICapabilityProvider ---------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt, false); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt, false); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
@Override
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof SolarPanelBlock)) return;
current_feedin_ = 0;
final List<SolarPanelTileEntity> adjacent_panels = new ArrayList<>();
if(output_enabled_) {
for(int i=0; (i<transfer_directions_.length) && (accumulated_power_>0); ++i) {
final Direction f = transfer_directions_[i];
TileEntity te = level.getBlockEntity(worldPosition.relative(f));
if(te==null) continue;
IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, f.getOpposite()).orElse(null);
if(es==null) continue;
if(!es.canReceive()) {
if(!(te instanceof SolarPanelTileEntity)) continue;
adjacent_panels.add((SolarPanelTileEntity)te);
continue;
}
final int feed_power = (accumulated_power_ > (max_power_storage_/10)) ? max_feed_power : Math.max(current_production_*2, (peak_power_per_tick_/4));
int fed = es.receiveEnergy(Math.min(accumulated_power_, feed_power * TICK_INTERVAL), false);
accumulated_power_ = MathHelper.clamp(accumulated_power_-fed,0, accumulated_power_);
current_feedin_ += fed;
}
}
current_feedin_ /= TICK_INTERVAL;
if((current_feedin_ <= 0) && ((accumulated_power_ >= balancing_threshold) || (current_production_ <= 0))) {
for(SolarPanelTileEntity panel: adjacent_panels) {
if(panel.accumulated_power_ >= (accumulated_power_-balancing_threshold)) continue;
panel.accumulated_power_ += balancing_threshold;
accumulated_power_ -= balancing_threshold;
if(accumulated_power_ < balancing_threshold) break;
}
}
if(!level.canSeeSkyFromBelowWater(worldPosition)) {
tick_timer_ = TICK_INTERVAL * 10;
current_production_ = 0;
if((accumulated_power_ > 0)) output_enabled_ = true;
if(state.getValue((SolarPanelBlock.EXPOSITION))!=2) level.setBlockAndUpdate(worldPosition, state.setValue(SolarPanelBlock.EXPOSITION, 2));
return;
}
if(accumulated_power_ <= 0) output_enabled_ = false;
if(--recalc_timer_ > 0) return;
recalc_timer_ = ACCUMULATION_INTERVAL + ((int)(Math.random()+.5));
int theta = ((((int)(level.getSunAngle(1f) * (180.0/Math.PI)))+90) % 360);
int e = 2;
if(theta > 340) e = 2;
else if(theta < 45) e = 0;
else if(theta < 80) e = 1;
else if(theta < 100) e = 2;
else if(theta < 135) e = 3;
else if(theta < 190) e = 4;
BlockState nstate = state.setValue(SolarPanelBlock.EXPOSITION, e);
if(nstate != state) level.setBlock(worldPosition, nstate, 1|2);
final double eff = (1.0-((level.getRainLevel(1f)*0.6)+(level.getThunderLevel(1f)*0.3)));
final double ll = ((double)(level.getLightEngine().getLayerListener(LightType.SKY).getLightValue(getBlockPos())))/15;
final double rf = Math.sin((Math.PI/2) * Math.sqrt(((double)(((theta<0)||(theta>180))?(0):((theta>90)?(180-theta):(theta))))/90));
current_production_ = (int)(Math.min(rf*rf*eff*ll, 1) * peak_power_per_tick_);
accumulated_power_ = Math.min(accumulated_power_ + (current_production_*(TICK_INTERVAL*ACCUMULATION_INTERVAL)), max_power_storage_);
if(accumulated_power_ >= (feeding_threshold)) output_enabled_ = true;
}
}
}
/*
* @file EdSolarPanel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdSolarPanel
{
public static final int DEFAULT_PEAK_POWER = 40;
private static int peak_power_per_tick_ = DEFAULT_PEAK_POWER;
private static int max_power_storage_ = 64000;
private static int max_feed_power = 4096;
private static int feeding_threshold = max_power_storage_/5;
private static int balancing_threshold = max_power_storage_/10;
public static void on_config(int peak_power_per_tick, int battery_capacity, int max_feed_in_power)
{
final int t = SolarPanelTileEntity.TICK_INTERVAL;
peak_power_per_tick_ = Mth.clamp(peak_power_per_tick, 12, 8192);
feeding_threshold = Math.max(max_power_storage_/5, 1000);
balancing_threshold = Math.max(max_power_storage_/10, 1000);
max_power_storage_ = battery_capacity;
max_feed_power = max_feed_in_power * t;
ModConfig.log("Config small solar panel: Peak production:" + peak_power_per_tick_ + "/t, capacity:" + max_power_storage_ + "rf, max-feed:" + (max_feed_power/t) + "rf/t");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelBlock extends StandardBlocks.Cutout implements StandardEntityBlocks.IStandardEntityBlock<SolarPanelTileEntity>
{
public static final IntegerProperty EXPOSITION = IntegerProperty.create("exposition", 0, 4);
public SolarPanelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(EXPOSITION, 1));
}
@Override
@Nullable
public BlockEntityType<EdSolarPanel.SolarPanelTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_SOLAR_PANEL; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EXPOSITION); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof SolarPanelTileEntity) ((SolarPanelTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int TICK_INTERVAL = 4;
public static final int ACCUMULATION_INTERVAL = 8;
private static final Direction[] transfer_directions_ = {Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH };
private int tick_timer_ = 0;
private int recalc_timer_ = 0;
private int current_production_ = 0;
private int current_feedin_ = 0;
private boolean output_enabled_ = false;
private final RfEnergy.Battery battery_ = new RfEnergy.Battery(max_power_storage_, 0, 1024);
private final LazyOptional<IEnergyStorage> energy_handler_ = battery_.createEnergyHandler();
//------------------------------------------------------------------------------------------------------------------
public SolarPanelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_SOLAR_PANEL, pos, state); }
public void readnbt(CompoundTag nbt, boolean update_packet)
{ battery_.load(nbt); }
protected void writenbt(CompoundTag nbt, boolean update_packet)
{ battery_.save(nbt); }
public void state_message(Player player)
{
String soc = Integer.toString(Mth.clamp((battery_.getEnergyStored()*100/max_power_storage_),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_solar_panel.status", soc, max_power_storage_, current_production_, current_feedin_));
}
// ICapabilityProvider ---------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt, false); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt, false); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
@Override
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof SolarPanelBlock)) return;
current_feedin_ = 0;
final List<SolarPanelTileEntity> adjacent_panels = new ArrayList<>();
if(output_enabled_) {
for(int i=0; (i<transfer_directions_.length) && (!battery_.isEmpty()); ++i) {
final Direction f = transfer_directions_[i];
BlockEntity te = level.getBlockEntity(worldPosition.relative(f));
if(te==null) continue;
IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, f.getOpposite()).orElse(null);
if(es==null) continue;
if(!es.canReceive()) {
if(!(te instanceof SolarPanelTileEntity)) continue;
adjacent_panels.add((SolarPanelTileEntity)te);
continue;
}
final int feed_power = (battery_.getEnergyStored() > (max_power_storage_/10)) ? max_feed_power : Math.max(current_production_*2, (peak_power_per_tick_/4));
final int fed = es.receiveEnergy(Math.min(battery_.getEnergyStored(), feed_power * TICK_INTERVAL), false);
battery_.draw(fed);
current_feedin_ += fed;
}
}
current_feedin_ /= TICK_INTERVAL;
if((current_feedin_ <= 0) && ((battery_.getEnergyStored() >= balancing_threshold) || (current_production_ <= 0))) {
for(SolarPanelTileEntity panel: adjacent_panels) {
if(panel.battery_.getEnergyStored() >= (battery_.getEnergyStored()-balancing_threshold)) continue;
panel.battery_.setEnergyStored(panel.battery_.getEnergyStored() + balancing_threshold);
battery_.setEnergyStored(battery_.getEnergyStored() - balancing_threshold);
if(battery_.getEnergyStored() < balancing_threshold) break;
}
}
if(!level.canSeeSkyFromBelowWater(worldPosition)) {
tick_timer_ = TICK_INTERVAL * 10;
current_production_ = 0;
if((!battery_.isEmpty())) output_enabled_ = true;
if(state.getValue((SolarPanelBlock.EXPOSITION))!=2) level.setBlockAndUpdate(worldPosition, state.setValue(SolarPanelBlock.EXPOSITION, 2));
return;
}
if(battery_.isEmpty()) output_enabled_ = false;
if(--recalc_timer_ > 0) return;
recalc_timer_ = ACCUMULATION_INTERVAL + ((int)(Math.random()+.5));
int theta = ((((int)(level.getSunAngle(1f) * (180.0/Math.PI)))+90) % 360);
int e = 2;
if(theta > 340) e = 2;
else if(theta < 45) e = 0;
else if(theta < 80) e = 1;
else if(theta < 100) e = 2;
else if(theta < 135) e = 3;
else if(theta < 190) e = 4;
BlockState nstate = state.setValue(SolarPanelBlock.EXPOSITION, e);
if(nstate != state) level.setBlock(worldPosition, nstate, 1|2);
final double eff = (1.0-((level.getRainLevel(1f)*0.6)+(level.getThunderLevel(1f)*0.3)));
final double ll = ((double)(level.getLightEngine().getLayerListener(LightLayer.SKY).getLightValue(getBlockPos())))/15;
final double rf = Math.sin((Math.PI/2) * Math.sqrt(((double)(((theta<0)||(theta>180))?(0):((theta>90)?(180-theta):(theta))))/90));
current_production_ = (int)(Math.min(rf*rf*eff*ll, 1) * peak_power_per_tick_);
battery_.setEnergyStored(Math.min(battery_.getEnergyStored() + (current_production_*(TICK_INTERVAL*ACCUMULATION_INTERVAL)), max_power_storage_));
if(battery_.getEnergyStored() >= (feeding_threshold)) output_enabled_ = true;
}
}
}

View file

@ -1,22 +0,0 @@
/*
* @file EdStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardStairsBlock;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
public class EdStairsBlock extends StandardStairsBlock implements IDecorBlock
{
public EdStairsBlock(long config, BlockState state, AbstractBlock.Properties properties)
{ super(config, state, properties); }
public EdStairsBlock(long config, java.util.function.Supplier<BlockState> state, AbstractBlock.Properties properties)
{ super(config, state, properties); }
}

View file

@ -1,84 +1,88 @@
/*
* @file EdStraightPoleBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.item.ItemStack;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.Arrays;
import net.minecraft.block.AbstractBlock;
public class EdStraightPoleBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
private final EdStraightPoleBlock default_pole;
public EdStraightPoleBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB, @Nullable EdStraightPoleBlock defaultPole)
{ super(config, builder, unrotatedAABB); default_pole=(defaultPole==null) ? (this) : (defaultPole); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
Direction facing = context.getClickedFace();
BlockState state = super.getStateForPlacement(context).setValue(FACING, facing);
if((config & DecorBlock.CFG_FLIP_PLACEMENT_IF_SAME) != 0) {
World world = context.getLevel();
BlockPos pos = context.getClickedPos();
if(world.getBlockState(pos.relative(facing.getOpposite())).getBlock() instanceof EdStraightPoleBlock) {
state = state.setValue(FACING, state.getValue(FACING).getOpposite());
}
}
return state;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if((hit.getDirection().getAxis() == state.getValue(FACING).getAxis())) return ActionResultType.PASS;
final ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (!(held_stack.getItem() instanceof BlockItem))) return ActionResultType.PASS;
if(!(((BlockItem)(held_stack.getItem())).getBlock() instanceof EdStraightPoleBlock)) return ActionResultType.PASS;
if(held_stack.getItem() != default_pole.asItem()) return ActionResultType.sidedSuccess(world.isClientSide());
final Block held_block = ((BlockItem)(held_stack.getItem())).getBlock();
final Direction block_direction = state.getValue(FACING);
final Vector3d block_vec = Vector3d.atLowerCornerOf(state.getValue(FACING).getNormal());
final double colinearity = 1.0-block_vec.cross(player.getLookAngle()).length();
final Direction placement_direction = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis()==block_direction.getAxis()).findFirst().orElse(Direction.NORTH);
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockItemUseContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return ActionResultType.sidedSuccess(world.isClientSide());
final BlockState new_state = held_block.getStateForPlacement(ctx);
if(new_state == null) return ActionResultType.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return ActionResultType.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
}
/*
* @file EdStraightPoleBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdStraightPoleBlock extends StandardBlocks.DirectedWaterLoggable
{
private final EdStraightPoleBlock default_pole;
public EdStraightPoleBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB, @Nullable EdStraightPoleBlock defaultPole)
{ super(config, builder, unrotatedAABB); default_pole=(defaultPole==null) ? (this) : (defaultPole); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
Direction facing = context.getClickedFace();
BlockState state = super.getStateForPlacement(context).setValue(FACING, facing);
if((config & DecorBlock.CFG_FLIP_PLACEMENT_IF_SAME) != 0) {
Level world = context.getLevel();
BlockPos pos = context.getClickedPos();
if(world.getBlockState(pos.relative(facing.getOpposite())).getBlock() instanceof EdStraightPoleBlock) {
state = state.setValue(FACING, state.getValue(FACING).getOpposite());
}
}
return state;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if((hit.getDirection().getAxis() == state.getValue(FACING).getAxis())) return InteractionResult.PASS;
final ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (!(held_stack.getItem() instanceof BlockItem))) return InteractionResult.PASS;
if(!(((BlockItem)(held_stack.getItem())).getBlock() instanceof EdStraightPoleBlock)) return InteractionResult.PASS;
if(held_stack.getItem() != default_pole.asItem()) return InteractionResult.sidedSuccess(world.isClientSide());
final Block held_block = ((BlockItem)(held_stack.getItem())).getBlock();
final Direction block_direction = state.getValue(FACING);
final Vec3 block_vec = Vec3.atLowerCornerOf(state.getValue(FACING).getNormal());
final double colinearity = 1.0-block_vec.cross(player.getLookAngle()).length();
final Direction placement_direction = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis()==block_direction.getAxis()).findFirst().orElse(Direction.NORTH);
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockPlaceContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return InteractionResult.sidedSuccess(world.isClientSide());
final BlockState new_state = held_block.getStateForPlacement(ctx);
if(new_state == null) return InteractionResult.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return InteractionResult.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
}

View file

@ -1,339 +1,335 @@
/*
* @file EdTestBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Creative mod testing block
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.registries.ForgeRegistries;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.*;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdTestBlock
{
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TestBlock extends DecorBlock.Directed implements Auxiliaries.IExperimentalFeature, IDecorBlock
{
public TestBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return VoxelShapes.block(); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new TestTileEntity(); }
@Override
public boolean canConnectRedstone(BlockState state, IBlockReader world, BlockPos pos, @Nullable Direction side)
{ return true; }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return Collections.singletonList(new ItemStack(this)); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof TestTileEntity)) return ActionResultType.FAIL;
return ((TestTileEntity)te).activated(player, hand, hit) ? ActionResultType.CONSUME : ActionResultType.PASS;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TestTileEntity extends TileEntity implements ITickableTileEntity
{
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
private final Inventories.StorageInventory inventory_;
private final LazyOptional<IItemHandler> item_handler_;
private int tick_timer = 0;
private int rf_fed_avg = 0;
private int rf_fed_total = 0;
private int rf_fed_acc = 0;
private int rf_received_avg = 0;
private int rf_received_total = 0;
private int liq_filled_avg = 0;
private int liq_filled_total = 0;
private int liq_filled_acc = 0;
private int liq_received_avg = 0;
private int liq_received_total = 0;
private int items_inserted_total = 0;
private int items_received_total = 0;
private int rf_feed_setting = 4096;
private FluidStack liq_fill_stack = new FluidStack(Fluids.WATER, 128);
private ItemStack insertion_item = ItemStack.EMPTY;
private Direction block_facing = Direction.NORTH;
private boolean paused = false;
public TestTileEntity()
{ this(ModContent.TET_TEST_BLOCK); }
public TestTileEntity(TileEntityType<?> te_type)
{
super(te_type);
battery_ = new RfEnergy.Battery((int)1e9, (int)1e9, 0, 0);
energy_handler_ = battery_.createEnergyHandler();
tank_ = new Fluidics.Tank((int)1e9);
fluid_handler_ = tank_.createFluidHandler();
inventory_ = new Inventories.StorageInventory(this, 1);
item_handler_ = Inventories.MappedItemHandler.createInsertionHandler(inventory_);
}
@Override
public void load(BlockState state, CompoundNBT nbt)
{
super.load(state, nbt);
tank_.load(nbt);
battery_.load(nbt);
rf_fed_avg = nbt.getInt("rf_fed_avg");
rf_fed_total = nbt.getInt("rf_fed_total");
rf_fed_acc = nbt.getInt("rf_fed_acc");
rf_received_avg = nbt.getInt("rf_received_avg");
rf_received_total = nbt.getInt("rf_received_total");
liq_filled_avg = nbt.getInt("liq_filled_avg");
liq_filled_total = nbt.getInt("liq_filled_total");
liq_filled_acc = nbt.getInt("liq_filled_acc");
liq_received_avg = nbt.getInt("liq_received_avg");
liq_received_total = nbt.getInt("liq_received_total");
rf_feed_setting = nbt.getInt("rf_feed_setting");
items_received_total = nbt.getInt("items_received_total");
items_inserted_total = nbt.getInt("items_inserted_total");
if(nbt.contains("liq_fill_stack")) liq_fill_stack = FluidStack.loadFluidStackFromNBT(nbt.getCompound("liq_fill_stack"));
if(nbt.contains("insertion_item")) insertion_item = ItemStack.of(nbt.getCompound("insertion_item"));
}
@Override
public CompoundNBT save(CompoundNBT nbt)
{
super.save(nbt);
tank_.save(nbt);
battery_.save(nbt);
nbt.putInt("rf_fed_avg", rf_fed_avg);
nbt.putInt("rf_fed_total", rf_fed_total);
nbt.putInt("rf_fed_acc", rf_fed_acc);
nbt.putInt("rf_received_avg", rf_received_avg);
nbt.putInt("rf_received_total", rf_received_total);
nbt.putInt("liq_filled_avg", liq_filled_avg);
nbt.putInt("liq_filled_total", liq_filled_total);
nbt.putInt("liq_filled_acc", liq_filled_acc);
nbt.putInt("liq_received_avg", liq_received_avg);
nbt.putInt("liq_received_total", liq_received_total);
nbt.putInt("rf_feed_setting", rf_feed_setting);
nbt.putInt("items_received_total", items_received_total);
nbt.putInt("items_inserted_total", items_inserted_total);
if(!liq_fill_stack.isEmpty()) nbt.put("liq_fill_stack", liq_fill_stack.writeToNBT(new CompoundNBT()));
if(!insertion_item.isEmpty()) nbt.put("insertion_item", insertion_item.save(new CompoundNBT()));
return nbt;
}
private FluidStack getFillFluid(ItemStack stack)
{
// intentionally not item fluid handler, only specific items.
if(stack.getItem() == Items.WATER_BUCKET) return new FluidStack(Fluids.WATER, 1000);
if(stack.getItem() == Items.LAVA_BUCKET) return new FluidStack(Fluids.LAVA, 1000);
return FluidStack.EMPTY;
}
private ItemStack getRandomItemstack()
{
final int n = (int)Math.floor(Math.random() * ForgeRegistries.ITEMS.getValues().size());
ItemStack stack = new ItemStack(ForgeRegistries.ITEMS.getValues().stream().skip(n).findAny().orElse(Items.COBBLESTONE));
stack.setCount((int)Math.floor(Math.random() * stack.getMaxStackSize()));
return stack;
}
public boolean activated(PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) {
ArrayList<String> msgs = new ArrayList<>();
if(rf_fed_avg > 0) msgs.add("-" + rf_fed_avg + "rf/t");
if(rf_fed_total > 0) msgs.add("-" + rf_fed_total + "rf");
if(rf_received_avg > 0) msgs.add("+" + rf_received_avg + "rf/t");
if(rf_received_total > 0) msgs.add("+" + rf_received_total + "rf");
if(liq_filled_avg > 0) msgs.add("-" + liq_filled_avg + "mb/t");
if(liq_filled_total > 0) msgs.add("-" + liq_filled_total + "mb");
if(liq_received_avg > 0) msgs.add("+" + liq_received_avg + "mb/t");
if(liq_received_total > 0) msgs.add("+" + liq_received_total + "mb");
if(items_received_total > 0) msgs.add("+" + items_received_total + "items");
if(items_inserted_total > 0) msgs.add("-" + items_inserted_total + "items");
if(msgs.isEmpty()) msgs.add("Nothing transferred yet.");
Overlay.show(player, new StringTextComponent(String.join(" | ", msgs)), 1000);
return true;
} else if(paused) {
if(!getFillFluid(held).isEmpty()) {
FluidStack fs = getFillFluid(held);
if(liq_fill_stack.isEmpty() || !liq_fill_stack.isFluidEqual(fs)) {
fs.setAmount(128);
liq_fill_stack = fs;
} else {
int amount = liq_fill_stack.getAmount() * 2;
if(amount > 4096) amount = 16;
liq_fill_stack.setAmount(amount);
}
if(liq_fill_stack.isEmpty()) {
Overlay.show(player, new StringTextComponent("Fluid fill: none"), 1000);
} else {
Overlay.show(player, new StringTextComponent("Fluid fill: " + liq_fill_stack.getAmount() + "mb/t of " + liq_fill_stack.getFluid().getRegistryName()), 1000);
}
} else if(held.getItem() == Items.REDSTONE) {
rf_feed_setting = (rf_feed_setting<<1) & 0x00fffff0;
if(rf_feed_setting == 0) rf_feed_setting = 0x10;
Overlay.show(player, new StringTextComponent("RF feed rate: " + rf_feed_setting + "rf/t"), 1000);
} else {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
insertion_item = held.copy();
Overlay.show(player, new StringTextComponent("Insertion item: " + (insertion_item.getItem()==Items.LEVER ? "random" : insertion_item.toString()) + "/s"), 1000);
}
}
return true;
} else {
return false;
}
}
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if((!paused) && (facing != block_facing)) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
if(capability ==CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
}
return super.getCapability(capability, facing);
}
@Override
public void tick()
{
if(level.isClientSide()) return;
block_facing = getBlockState().getValue(TestBlock.FACING);
paused = level.hasNeighborSignal(getBlockPos());
if(!paused) {
boolean dirty = false;
{
int p = RfEnergy.feed(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), rf_feed_setting);
rf_fed_acc += p;
dirty |= p>0;
}
if(!liq_fill_stack.isEmpty()) {
int f = Fluidics.fill(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), liq_fill_stack);
liq_filled_acc += f;
dirty |= f>0;
}
if(!inventory_.isEmpty()) {
int i = inventory_.getItem(0).getCount();
items_received_total += i;
inventory_.clearContent();
dirty |= i>0;
}
if((tick_timer == 1) && (!insertion_item.isEmpty())) {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
ItemStack stack = (insertion_item.getItem()==Items.LEVER) ? getRandomItemstack() : insertion_item.copy();
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
ItemStack remaining = Inventories.insert(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), stack, false);
int n = stack.getCount() - remaining.getCount();
items_inserted_total += n;
dirty |= n>0;
}
}
if(dirty) {
setChanged();
}
}
if(--tick_timer <= 0) {
tick_timer = 20;
rf_fed_avg = rf_fed_acc/20;
rf_fed_total += rf_fed_acc;
rf_fed_acc = 0;
rf_received_avg = battery_.getEnergyStored()/20;
rf_received_total += battery_.getEnergyStored();
battery_.clear();
liq_received_avg = tank_.getFluidAmount();
liq_received_total += tank_.getFluidAmount();
tank_.clear();
liq_filled_avg = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_acc/20);
liq_filled_total = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_total+liq_filled_acc);
liq_filled_acc = 0;
}
}
}
}
/*
* @file EdTestBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Creative mod testing block
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.registries.ForgeRegistries;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.*;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdTestBlock
{
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TestBlock extends StandardBlocks.Directed implements StandardEntityBlocks.IStandardEntityBlock<TestTileEntity>, Auxiliaries.IExperimentalFeature
{
public TestBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
@Nullable
public BlockEntityType<EdTestBlock.TestTileEntity> getBlockEntityType()
{ return ModContent.TET_TEST_BLOCK; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return Shapes.block(); }
@Override
@SuppressWarnings("deprecation") // public boolean canConnectRedstone(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { return true; }
public boolean isSignalSource(BlockState p_60571_)
{ return true; }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return Collections.singletonList(new ItemStack(this)); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof TestTileEntity)) return InteractionResult.FAIL;
return ((TestTileEntity)te).activated(player, hand, hit) ? InteractionResult.CONSUME : InteractionResult.PASS;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TestTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
private final Inventories.StorageInventory inventory_;
private final LazyOptional<IItemHandler> item_handler_;
private int tick_timer = 0;
private int rf_fed_avg = 0;
private int rf_fed_total = 0;
private int rf_fed_acc = 0;
private int rf_received_avg = 0;
private int rf_received_total = 0;
private int liq_filled_avg = 0;
private int liq_filled_total = 0;
private int liq_filled_acc = 0;
private int liq_received_avg = 0;
private int liq_received_total = 0;
private int items_inserted_total = 0;
private int items_received_total = 0;
private int rf_feed_setting = 4096;
private FluidStack liq_fill_stack = new FluidStack(Fluids.WATER, 128);
private ItemStack insertion_item = ItemStack.EMPTY;
private Direction block_facing = Direction.NORTH;
private boolean paused = false;
public TestTileEntity(BlockPos pos, BlockState state)
{
super(ModContent.TET_TEST_BLOCK, pos, state);
battery_ = new RfEnergy.Battery((int)1e9, (int)1e9, 0, 0);
energy_handler_ = battery_.createEnergyHandler();
tank_ = new Fluidics.Tank((int)1e9);
fluid_handler_ = tank_.createFluidHandler();
inventory_ = new Inventories.StorageInventory(this, 1);
item_handler_ = Inventories.MappedItemHandler.createInsertionHandler(inventory_);
}
@Override
public void load(CompoundTag nbt)
{
super.load(nbt);
tank_.load(nbt);
battery_.load(nbt);
rf_fed_avg = nbt.getInt("rf_fed_avg");
rf_fed_total = nbt.getInt("rf_fed_total");
rf_fed_acc = nbt.getInt("rf_fed_acc");
rf_received_avg = nbt.getInt("rf_received_avg");
rf_received_total = nbt.getInt("rf_received_total");
liq_filled_avg = nbt.getInt("liq_filled_avg");
liq_filled_total = nbt.getInt("liq_filled_total");
liq_filled_acc = nbt.getInt("liq_filled_acc");
liq_received_avg = nbt.getInt("liq_received_avg");
liq_received_total = nbt.getInt("liq_received_total");
rf_feed_setting = nbt.getInt("rf_feed_setting");
items_received_total = nbt.getInt("items_received_total");
items_inserted_total = nbt.getInt("items_inserted_total");
if(nbt.contains("liq_fill_stack")) liq_fill_stack = FluidStack.loadFluidStackFromNBT(nbt.getCompound("liq_fill_stack"));
if(nbt.contains("insertion_item")) insertion_item = ItemStack.of(nbt.getCompound("insertion_item"));
}
@Override
public CompoundTag save(CompoundTag nbt)
{
super.save(nbt);
tank_.save(nbt);
battery_.save(nbt);
nbt.putInt("rf_fed_avg", rf_fed_avg);
nbt.putInt("rf_fed_total", rf_fed_total);
nbt.putInt("rf_fed_acc", rf_fed_acc);
nbt.putInt("rf_received_avg", rf_received_avg);
nbt.putInt("rf_received_total", rf_received_total);
nbt.putInt("liq_filled_avg", liq_filled_avg);
nbt.putInt("liq_filled_total", liq_filled_total);
nbt.putInt("liq_filled_acc", liq_filled_acc);
nbt.putInt("liq_received_avg", liq_received_avg);
nbt.putInt("liq_received_total", liq_received_total);
nbt.putInt("rf_feed_setting", rf_feed_setting);
nbt.putInt("items_received_total", items_received_total);
nbt.putInt("items_inserted_total", items_inserted_total);
if(!liq_fill_stack.isEmpty()) nbt.put("liq_fill_stack", liq_fill_stack.writeToNBT(new CompoundTag()));
if(!insertion_item.isEmpty()) nbt.put("insertion_item", insertion_item.save(new CompoundTag()));
return nbt;
}
private FluidStack getFillFluid(ItemStack stack)
{
// intentionally not item fluid handler, only specific items.
if(stack.getItem() == Items.WATER_BUCKET) return new FluidStack(Fluids.WATER, 1000);
if(stack.getItem() == Items.LAVA_BUCKET) return new FluidStack(Fluids.LAVA, 1000);
return FluidStack.EMPTY;
}
private ItemStack getRandomItemstack()
{
final int n = (int)Math.floor(Math.random() * ForgeRegistries.ITEMS.getValues().size());
ItemStack stack = new ItemStack(ForgeRegistries.ITEMS.getValues().stream().skip(n).findAny().orElse(Items.COBBLESTONE));
stack.setCount((int)Math.floor(Math.random() * stack.getMaxStackSize()));
return stack;
}
public boolean activated(Player player, InteractionHand hand, BlockHitResult hit)
{
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) {
ArrayList<String> msgs = new ArrayList<>();
if(rf_fed_avg > 0) msgs.add("-" + rf_fed_avg + "rf/t");
if(rf_fed_total > 0) msgs.add("-" + rf_fed_total + "rf");
if(rf_received_avg > 0) msgs.add("+" + rf_received_avg + "rf/t");
if(rf_received_total > 0) msgs.add("+" + rf_received_total + "rf");
if(liq_filled_avg > 0) msgs.add("-" + liq_filled_avg + "mb/t");
if(liq_filled_total > 0) msgs.add("-" + liq_filled_total + "mb");
if(liq_received_avg > 0) msgs.add("+" + liq_received_avg + "mb/t");
if(liq_received_total > 0) msgs.add("+" + liq_received_total + "mb");
if(items_received_total > 0) msgs.add("+" + items_received_total + "items");
if(items_inserted_total > 0) msgs.add("-" + items_inserted_total + "items");
if(msgs.isEmpty()) msgs.add("Nothing transferred yet.");
Overlay.show(player, new TextComponent(String.join(" | ", msgs)), 1000);
return true;
} else if(paused) {
if(!getFillFluid(held).isEmpty()) {
FluidStack fs = getFillFluid(held);
if(liq_fill_stack.isEmpty() || !liq_fill_stack.isFluidEqual(fs)) {
fs.setAmount(128);
liq_fill_stack = fs;
} else {
int amount = liq_fill_stack.getAmount() * 2;
if(amount > 4096) amount = 16;
liq_fill_stack.setAmount(amount);
}
if(liq_fill_stack.isEmpty()) {
Overlay.show(player, new TextComponent("Fluid fill: none"), 1000);
} else {
Overlay.show(player, new TextComponent("Fluid fill: " + liq_fill_stack.getAmount() + "mb/t of " + liq_fill_stack.getFluid().getRegistryName()), 1000);
}
} else if(held.getItem() == Items.REDSTONE) {
rf_feed_setting = (rf_feed_setting<<1) & 0x00fffff0;
if(rf_feed_setting == 0) rf_feed_setting = 0x10;
Overlay.show(player, new TextComponent("RF feed rate: " + rf_feed_setting + "rf/t"), 1000);
} else {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
insertion_item = held.copy();
Overlay.show(player, new TextComponent("Insertion item: " + (insertion_item.getItem()==Items.LEVER ? "random" : insertion_item.toString()) + "/s"), 1000);
}
}
return true;
} else {
return false;
}
}
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if((!paused) && (facing != block_facing)) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
if(capability ==CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
}
return super.getCapability(capability, facing);
}
@Override
public void tick()
{
if(level.isClientSide()) return;
block_facing = getBlockState().getValue(TestBlock.FACING);
paused = level.hasNeighborSignal(getBlockPos());
if(!paused) {
boolean dirty = false;
{
int p = RfEnergy.feed(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), rf_feed_setting);
rf_fed_acc += p;
dirty |= p>0;
}
if(!liq_fill_stack.isEmpty()) {
int f = Fluidics.fill(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), liq_fill_stack);
liq_filled_acc += f;
dirty |= f>0;
}
if(!inventory_.isEmpty()) {
int i = inventory_.getItem(0).getCount();
items_received_total += i;
inventory_.clearContent();
dirty |= i>0;
}
if((tick_timer == 1) && (!insertion_item.isEmpty())) {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
ItemStack stack = (insertion_item.getItem()==Items.LEVER) ? getRandomItemstack() : insertion_item.copy();
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
ItemStack remaining = Inventories.insert(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), stack, false);
int n = stack.getCount() - remaining.getCount();
items_inserted_total += n;
dirty |= n>0;
}
}
if(dirty) {
setChanged();
}
}
if(--tick_timer <= 0) {
tick_timer = 20;
rf_fed_avg = rf_fed_acc/20;
rf_fed_total += rf_fed_acc;
rf_fed_acc = 0;
rf_received_avg = battery_.getEnergyStored()/20;
rf_received_total += battery_.getEnergyStored();
battery_.clear();
liq_received_avg = tank_.getFluidAmount();
liq_received_total += tank_.getFluidAmount();
tank_.clear();
liq_filled_avg = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_acc/20);
liq_filled_total = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_total+liq_filled_acc);
liq_filled_acc = 0;
}
}
}
}

View file

@ -1,274 +1,270 @@
/*
* @file EdTreeCutter.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Tree Cutter
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.detail.TreeCutting;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.Random;
public class EdTreeCutter
{
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{ TreeCutterTileEntity.on_config(boost_energy_per_tick, cutting_time_seconds,power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterBlock extends DecorBlock.Horizontal implements IDecorBlock
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public TreeCutterBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new TreeCutterTileEntity(); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
final double rv = rnd.nextDouble();
if(rv > 0.8) return;
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST: world.addParticle(ParticleTypes.SMOKE, x-xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case EAST: world.addParticle(ParticleTypes.SMOKE, x+xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case NORTH: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z-xc, 0.0, 0.0, 0.0); break;
default: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z+xc, 0.0, 0.0, 0.0); break;
}
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof TreeCutterTileEntity) ((TreeCutterTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterTileEntity extends TileEntity implements ITickableTileEntity, IEnergyStorage
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 6;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_CUTTING_TIME_NEEDED = 60; // 60 secs, so that people don't come to the bright idea to carry one with them.
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = DEFAULT_BOOST_ENERGY * 20;
private static int cutting_time_needed = 20 * DEFAULT_CUTTING_TIME_NEEDED;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int energy_;
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * MathHelper.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 10000);
cutting_time_needed = 20 * MathHelper.clamp(cutting_time_seconds, 10, 240);
requires_power = power_required;
ModConfig.log("Config tree cutter: energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t" + (requires_power?" (power required for operation) ":"") + ", cutting time:" + cutting_time_needed + "t." );
}
public TreeCutterTileEntity()
{ super(ModContent.TET_SMALL_TREE_CUTTER); }
public TreeCutterTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public void readnbt(CompoundNBT nbt)
{ energy_ = nbt.getInt("energy"); }
private void writenbt(CompoundNBT nbt)
{ nbt.putInt("energy", energy_); }
public void state_message(PlayerEntity player)
{
String progress = "0";
if((active_timer_ > 0) && (cutting_time_needed > 0) && (active_timer_ > 0)) {
progress = Integer.toString((int)MathHelper.clamp((((double)proc_time_elapsed_) / ((double)cutting_time_needed) * 100), 0, 100));
}
String soc = Integer.toString(MathHelper.clamp((energy_*100/energy_max),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_tree_cutter.status", new Object[]{soc, energy_max, progress, (cutting_time_needed/20) }));
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return boost_energy_consumption*2; }
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
maxReceive = MathHelper.clamp(maxReceive, 0, Math.max((energy_max) - energy_, 0));
if(!simulate) energy_ += maxReceive;
return maxReceive;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(--tick_timer_ > 0) return;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof TreeCutterBlock)) { tick_timer_ = TICK_INTERVAL; return; }
if(level.isClientSide) {
if(!device_state.getValue(TreeCutterBlock.ACTIVE)) {
tick_timer_ = TICK_INTERVAL;
} else {
tick_timer_ = 1;
level.playLocalSound(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_HIT, SoundCategory.BLOCKS, 0.1f, 1.0f, false);
}
} else {
tick_timer_ = TICK_INTERVAL;
final BlockPos tree_pos = worldPosition.relative(device_state.getValue(TreeCutterBlock.HORIZONTAL_FACING));
final BlockState tree_state = level.getBlockState(tree_pos);
if(!TreeCutting.canChop(tree_state) || (level.hasNeighborSignal(worldPosition))) {
if(device_state.getValue(TreeCutterBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
active_timer_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
proc_time_elapsed_ += TICK_INTERVAL;
if(energy_ >= boost_energy_consumption) {
energy_ -= boost_energy_consumption;
proc_time_elapsed_ += TICK_INTERVAL*BOOST_FACTOR;
active_timer_ = 2;
} else if(!requires_power) {
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= cutting_time_needed) {
proc_time_elapsed_ = 0;
TreeCutting.chopTree(level, tree_state, tree_pos, 512, false);
level.playSound(null, worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_BREAK, SoundCategory.BLOCKS, 1.0f, 1.0f);
active = false;
}
if(device_state.getValue(TreeCutterBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, active), 1|2);
}
}
}
}
}
/*
* @file EdTreeCutter.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Tree Cutter
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.detail.TreeCutting;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.Random;
public class EdTreeCutter
{
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{ TreeCutterTileEntity.on_config(boost_energy_per_tick, cutting_time_seconds,power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterBlock extends StandardBlocks.Horizontal implements StandardEntityBlocks.IStandardEntityBlock<TreeCutterTileEntity>
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public TreeCutterBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
@Nullable
public BlockEntityType<EdTreeCutter.TreeCutterTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_TREE_CUTTER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
// Sound
if(true || (world.getGameTime() & 0x1) == 0) {
world.playLocalSound(pos.getX(), pos.getY(), pos.getZ(), SoundEvents.WOOD_HIT, SoundSource.BLOCKS, 0.1f, 1.0f, false);
}
// Particles
{
final double rv = rnd.nextDouble();
if(rv < 0.8) {
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST -> world.addParticle(ParticleTypes.SMOKE, x - xc, yr, z + xr, 0.0, 0.0, 0.0);
case EAST -> world.addParticle(ParticleTypes.SMOKE, x + xc, yr, z + xr, 0.0, 0.0, 0.0);
case NORTH -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z - xc, 0.0, 0.0, 0.0);
default -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z + xc, 0.0, 0.0, 0.0);
}
}
}
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof TreeCutterTileEntity) ((TreeCutterTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterTileEntity extends StandardEntityBlocks.StandardBlockEntity implements IEnergyStorage
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 6;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_CUTTING_TIME_NEEDED = 60; // 60 secs, so that people don't come to the bright idea to carry one with them.
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = DEFAULT_BOOST_ENERGY * 20;
private static int cutting_time_needed = 20 * DEFAULT_CUTTING_TIME_NEEDED;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int energy_;
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * Mth.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 10000);
cutting_time_needed = 20 * Mth.clamp(cutting_time_seconds, 10, 240);
requires_power = power_required;
ModConfig.log("Config tree cutter: energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t" + (requires_power?" (power required for operation) ":"") + ", cutting time:" + cutting_time_needed + "t." );
}
public TreeCutterTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_TREE_CUTTER, pos, state); }
public void readnbt(CompoundTag nbt)
{ energy_ = nbt.getInt("energy"); }
private void writenbt(CompoundTag nbt)
{ nbt.putInt("energy", energy_); }
public void state_message(Player player)
{
String progress = "0";
if((active_timer_ > 0) && (cutting_time_needed > 0) && (active_timer_ > 0)) {
progress = Integer.toString((int)Mth.clamp((((double)proc_time_elapsed_) / ((double)cutting_time_needed) * 100), 0, 100));
}
String soc = Integer.toString(Mth.clamp((energy_*100/energy_max),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_tree_cutter.status", soc, energy_max, progress, (cutting_time_needed/20)));
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return boost_energy_consumption*2; }
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
maxReceive = Mth.clamp(maxReceive, 0, Math.max((energy_max) - energy_, 0));
if(!simulate) energy_ += maxReceive;
return maxReceive;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof TreeCutterBlock)) return;
final BlockPos tree_pos = worldPosition.relative(device_state.getValue(TreeCutterBlock.HORIZONTAL_FACING));
final BlockState tree_state = level.getBlockState(tree_pos);
if(!TreeCutting.canChop(tree_state) || (level.hasNeighborSignal(worldPosition))) {
if(device_state.getValue(TreeCutterBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
active_timer_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
proc_time_elapsed_ += TICK_INTERVAL;
if(energy_ >= boost_energy_consumption) {
energy_ -= boost_energy_consumption;
proc_time_elapsed_ += TICK_INTERVAL*BOOST_FACTOR;
active_timer_ = 2;
} else if(!requires_power) {
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= cutting_time_needed) {
proc_time_elapsed_ = 0;
TreeCutting.chopTree(level, tree_state, tree_pos, 512, false);
level.playSound(null, worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_BREAK, SoundSource.BLOCKS, 1.0f, 1.0f);
active = false;
}
if(device_state.getValue(TreeCutterBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, active), 1|2);
}
}
}
}

View file

@ -1,31 +1,35 @@
/*
* @file EdWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader;
import wile.engineersdecor.libmc.blocks.VariantWallBlock;
public class EdWallBlock extends VariantWallBlock implements IDecorBlock
{
public EdWallBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
if(facingState==null) return false;
if(super.attachesTo(facingState, world, facingPos, side)) return true;
if(facingState.getBlock() instanceof EdWindowBlock) return true;
if(facingState.getBlock() instanceof PaneBlock) return true;
return false;
}
}
/*
* @file EdWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.IronBarsBlock;
import net.minecraft.world.level.block.StainedGlassPaneBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.libmc.blocks.VariantWallBlock;
public class EdWallBlock extends VariantWallBlock
{
public EdWallBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
if(facingState==null) return false;
if(super.attachesTo(facingState, world, facingPos, side)) return true;
if(facingState.getBlock() instanceof EdWindowBlock) return true;
if(facingState.getBlock() instanceof IronBarsBlock) return true;
if(facingState.getBlock() instanceof StainedGlassPaneBlock) return true;
return false;
}
}

View file

@ -1,88 +1,93 @@
/*
* @file EdWindowBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Mod windows.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdWindowBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
public EdWindowBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
Direction facing = context.getHorizontalDirection();
if(Math.abs(context.getPlayer().getLookAngle().y) > 0.9) {
facing = context.getNearestLookingDirection();
} else {
for(Direction f: Direction.values()) {
BlockState st = context.getLevel().getBlockState(context.getClickedPos().relative(f));
if(st.getBlock() == this) {
facing = st.getValue(FACING);
break;
}
}
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return ActionResultType.PASS;
final Direction facing = state.getValue(FACING);
if(facing.getAxis() != hit.getDirection().getAxis()) return ActionResultType.PASS;
Arrays.stream(Direction.orderedByNearest(player))
.filter(d->d.getAxis() != facing.getAxis())
.filter(d->world.getBlockState(pos.relative(d)).canBeReplaced((new DirectionalPlaceContext(world, pos.relative(d), facing.getOpposite(), player.getItemInHand(hand), facing))))
.findFirst().ifPresent((d)->{
BlockState st = defaultBlockState()
.setValue(FACING, facing)
.setValue(WATERLOGGED,world.getBlockState(pos.relative(d)).getFluidState().getType()==Fluids.WATER);
world.setBlock(pos.relative(d), st, 1|2);
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
player.getItemInHand(hand).shrink(1);
}
);
return ActionResultType.sidedSuccess(world.isClientSide());
}
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return true; }
}
/*
* @file EdWindowBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Mod windows.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdWindowBlock extends StandardBlocks.DirectedWaterLoggable
{
public EdWindowBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
Direction facing = context.getHorizontalDirection();
if(Math.abs(context.getPlayer().getLookAngle().y) > 0.9) {
facing = context.getNearestLookingDirection();
} else {
for(Direction f: Direction.values()) {
BlockState st = context.getLevel().getBlockState(context.getClickedPos().relative(f));
if(st.getBlock() == this) {
facing = st.getValue(FACING);
break;
}
}
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return InteractionResult.PASS;
final Direction facing = state.getValue(FACING);
if(facing.getAxis() != hit.getDirection().getAxis()) return InteractionResult.PASS;
Arrays.stream(Direction.orderedByNearest(player))
.filter(d->d.getAxis() != facing.getAxis())
.filter(d->world.getBlockState(pos.relative(d)).canBeReplaced((new DirectionalPlaceContext(world, pos.relative(d), facing.getOpposite(), player.getItemInHand(hand), facing))))
.findFirst().ifPresent((d)->{
BlockState st = defaultBlockState()
.setValue(FACING, facing)
.setValue(WATERLOGGED,world.getBlockState(pos.relative(d)).getFluidState().getType()==Fluids.WATER);
world.setBlock(pos.relative(d), st, 1|2);
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
player.getItemInHand(hand).shrink(1);
}
);
return InteractionResult.sidedSuccess(world.isClientSide());
}
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return true; }
}

View file

@ -1,15 +0,0 @@
/*
* @file IDecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Interface for tagging and common default behaviour.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
public interface IDecorBlock extends StandardBlocks.IStandardBlock
{
}

View file

@ -1,22 +0,0 @@
/*
* @file ExtItems.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Object holder based item references.
*/
package wile.engineersdecor.detail;
import net.minecraft.item.Item;
import net.minecraftforge.registries.ObjectHolder;
public class ExternalObjects
{
@ObjectHolder("bottledmilk:milk_bottle_drinkable")
public static final Item BOTTLED_MILK_BOTTLE_DRINKLABLE = null;
public static final void onPostInit()
{}
}

View file

@ -1,165 +1,169 @@
/*
* @file ModTesrs.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Yet unstructured initial experiments with TESRs.
* May be structured after I know what I am doing there.
*/
package wile.engineersdecor.detail;
import net.minecraft.util.text.ITextComponent;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.blocks.EdCraftingTable;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.*;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.mojang.blaze3d.matrix.MatrixStack;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableBlock;
import wile.engineersdecor.blocks.EdLabeledCrate;
public class ModRenderers
{
//--------------------------------------------------------------------------------------------------------------------
// InvisibleEntityRenderer
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class InvisibleEntityRenderer<T extends Entity> extends EntityRenderer<T>
{
private final Minecraft mc = Minecraft.getInstance();
public InvisibleEntityRenderer(EntityRendererManager renderManagerIn)
{ super(renderManagerIn); }
public void render(T entity, float entityYaw, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight)
{}
public Vector3d getRenderOffset(T entity, float partialTicks)
{ return Vector3d.ZERO; }
@SuppressWarnings("deprecation")
public ResourceLocation getTextureLocation(T entity)
{ return AtlasTexture.LOCATION_BLOCKS; }
protected boolean shouldShowName(T entity)
{ return false; }
protected void renderNameTag(T entity, ITextComponent displayName, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight)
{}
}
//--------------------------------------------------------------------------------------------------------------------
// Crafting table
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class CraftingTableTer extends TileEntityRenderer<EdCraftingTable.CraftingTableTileEntity>
{
private static int tesr_error_counter = 4;
private static float scaler = 0.1f;
private static float gap = 0.19f;
private static float yrotations[] = {0, 90, 180, 270}; // [hdirection] S-W-N-E
private static float offsets[][][] = { // [hdirection][slotindex][xz]
{ {-1,-1},{+0,-1},{+1,-1}, {-1,+0},{+0,+0},{+1,+0}, {-1,+1},{+0,+1},{+1,+1} }, // S
{ {+1,-1},{+1,+0},{+1,+1}, {+0,-1},{+0,+0},{+0,+1}, {-1,-1},{-1,+0},{-1,+1} }, // W
{ {+1,+1},{+0,+1},{-1,+1}, {+1,+0},{+0,+0},{-1,+0}, {+1,-1},{+0,-1},{-1,-1} }, // N
{ {-1,+1},{-1,+0},{-1,-1}, {+0,+1},{+0,+0},{+0,-1}, {+1,+1},{+1,+0},{+1,-1} }, // E
};
public CraftingTableTer(TileEntityRendererDispatcher dispatcher)
{ super(dispatcher); }
@Override
@SuppressWarnings("deprecation")
public void render(final EdCraftingTable.CraftingTableTileEntity te, float unused1, MatrixStack mxs, IRenderTypeBuffer buf, int i5, int i6)
{
if(tesr_error_counter <= 0) return;
try {
final int di = MathHelper.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(CraftingTableBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
long posrnd = te.getBlockPos().asLong();
posrnd = (posrnd>>16)^(posrnd<<1);
for(int i=0; i<9; ++i) {
final ItemStack stack = te.mainInventory().getItem(i);
if(stack.isEmpty()) continue;
float prnd = ((float)(((Integer.rotateRight(stack.getItem().hashCode()^(int)posrnd,(stack.getCount()+i)&31)))&1023))/1024f;
float rndo = gap * ((prnd*0.1f)-0.05f);
float ox = gap * offsets[di][i][0], oz = gap * offsets[di][i][1];
float oy = 0.5f;
float ry = ((yrotations[di]+180) + ((prnd*60)-30)) % 360;
if(stack.isEmpty()) return;
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.XP.rotationDegrees(90.0f));
mxs.mulPose(Vector3f.ZP.rotationDegrees(ry));
mxs.translate(rndo, rndo, 0);
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType.FIXED, i5, i6, mxs, buf);
mxs.popPose();
}
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled because broken, exception was: " + e.getMessage());
ModEngineersDecor.logger().error(e.getStackTrace());
}
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Labeled Crate
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class DecorLabeledCrateTer extends TileEntityRenderer<EdLabeledCrate.LabeledCrateTileEntity>
{
private static int tesr_error_counter = 4;
private static float scaler = 0.35f;
double tr[][]= { // [hdirection=S-W-N-E][param]
{ +8.0/32, -8.0/32, +15.5/32, 180.0 }, // N
{ -15.5/32, -8.0/32, +8.0/32, 90.0 }, // E
{ -8.0/32, -8.0/32, -15.5/32, 0.0 }, // S param=tx,ty,tz,ry
{ +15.5/32, -8.0/32, -8.0/32, 270.0 }, // W
};
public DecorLabeledCrateTer(TileEntityRendererDispatcher dispatcher)
{ super(dispatcher); }
@Override
@SuppressWarnings("deprecation")
public void render(final EdLabeledCrate.LabeledCrateTileEntity te, float unused1, MatrixStack mxs, IRenderTypeBuffer buf, int i5, int i6)
{
if(tesr_error_counter<=0) return;
try {
final ItemStack stack = te.getItemFrameStack();
if(stack.isEmpty()) return;
final int di = MathHelper.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(EdLabeledCrate.LabeledCrateBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
double ox = tr[di][0], oy = tr[di][1], oz = tr[di][2];
float ry = (float)tr[di][3];
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.YP.rotationDegrees(ry));
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType.FIXED, i5, i6, mxs, buf);
mxs.popPose();
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled (because broken), exception was: " + e.getMessage());
}
}
}
}
}
/*
* @file ModTesrs.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Yet unstructured initial experiments with TESRs.
* May be structured after I know what I am doing there.
*/
package wile.engineersdecor.detail;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Vector3f;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.blocks.EdCraftingTable;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableBlock;
import wile.engineersdecor.blocks.EdLabeledCrate;
public class ModRenderers
{
//--------------------------------------------------------------------------------------------------------------------
// InvisibleEntityRenderer
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class InvisibleEntityRenderer<T extends Entity> extends EntityRenderer<T>
{
private final Minecraft mc = Minecraft.getInstance();
public InvisibleEntityRenderer(EntityRendererProvider.Context context)
{ super(context); }
public void render(T entity, float entityYaw, float partialTicks, PoseStack matrixStack, MultiBufferSource buffer, int packedLight)
{}
public Vec3 getRenderOffset(T entity, float partialTicks)
{ return Vec3.ZERO; }
@SuppressWarnings("deprecation")
public ResourceLocation getTextureLocation(T entity)
{ return TextureAtlas.LOCATION_BLOCKS; }
protected boolean shouldShowName(T entity)
{ return false; }
protected void renderNameTag(T entity, Component displayName, PoseStack matrixStack, MultiBufferSource buffer, int packedLight)
{}
}
//--------------------------------------------------------------------------------------------------------------------
// Crafting table
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class CraftingTableTer implements BlockEntityRenderer<EdCraftingTable.CraftingTableTileEntity>
{
private static int tesr_error_counter = 4;
private static final float scaler = 0.1f;
private static final float gap = 0.19f;
private static final float[] yrotations = {0, 90, 180, 270}; // [hdirection] S-W-N-E
private static final float[][][] offsets = { // [hdirection][slotindex][xz]
{ {-1,-1},{+0,-1},{+1,-1}, {-1,+0},{+0,+0},{+1,+0}, {-1,+1},{+0,+1},{+1,+1} }, // S
{ {+1,-1},{+1,+0},{+1,+1}, {+0,-1},{+0,+0},{+0,+1}, {-1,-1},{-1,+0},{-1,+1} }, // W
{ {+1,+1},{+0,+1},{-1,+1}, {+1,+0},{+0,+0},{-1,+0}, {+1,-1},{+0,-1},{-1,-1} }, // N
{ {-1,+1},{-1,+0},{-1,-1}, {+0,+1},{+0,+0},{+0,-1}, {+1,+1},{+1,+0},{+1,-1} }, // E
};
private final BlockEntityRendererProvider.Context renderer_;
public CraftingTableTer(BlockEntityRendererProvider.Context renderer)
{ this.renderer_ = renderer; }
@Override
@SuppressWarnings("deprecation")
public void render(final EdCraftingTable.CraftingTableTileEntity te, float unused1, PoseStack mxs, MultiBufferSource buf, int i5, int overlayTexture)
{
if(tesr_error_counter <= 0) return;
try {
final int di = Mth.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(CraftingTableBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
long posrnd = te.getBlockPos().asLong();
posrnd = (posrnd>>16)^(posrnd<<1);
for(int i=0; i<9; ++i) {
final ItemStack stack = te.mainInventory().getItem(i);
if(stack.isEmpty()) continue;
float prnd = ((float)(((Integer.rotateRight(stack.getItem().hashCode()^(int)posrnd,(stack.getCount()+i)&31)))&1023))/1024f;
float rndo = gap * ((prnd*0.1f)-0.05f);
float ox = gap * offsets[di][i][0], oz = gap * offsets[di][i][1];
float oy = 0.5f;
float ry = ((yrotations[di]+180) + ((prnd*60)-30)) % 360;
if(stack.isEmpty()) return;
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.XP.rotationDegrees(90.0f));
mxs.mulPose(Vector3f.ZP.rotationDegrees(ry));
mxs.translate(rndo, rndo, 0);
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, ItemTransforms.TransformType.FIXED, i5, overlayTexture, mxs, buf, 0);
mxs.popPose();
}
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled because broken, exception was: " + e.getMessage());
ModEngineersDecor.logger().error(e.getStackTrace());
}
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Labeled Crate
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class DecorLabeledCrateTer implements BlockEntityRenderer<EdLabeledCrate.LabeledCrateTileEntity>
{
private static int tesr_error_counter = 4;
private static final float scaler = 0.35f;
private static final double[][] tr = { // [hdirection=S-W-N-E][param]
{ +8.0/32, -8.0/32, +15.5/32, 180.0 }, // N
{ -15.5/32, -8.0/32, +8.0/32, 90.0 }, // E
{ -8.0/32, -8.0/32, -15.5/32, 0.0 }, // S param=tx,ty,tz,ry
{ +15.5/32, -8.0/32, -8.0/32, 270.0 }, // W
};
private final BlockEntityRendererProvider.Context renderer_;
public DecorLabeledCrateTer(BlockEntityRendererProvider.Context renderer)
{ this.renderer_ = renderer; }
@Override
@SuppressWarnings("deprecation")
public void render(final EdLabeledCrate.LabeledCrateTileEntity te, float unused1, PoseStack mxs, MultiBufferSource buf, int i5, int overlayTexture)
{
if(tesr_error_counter<=0) return;
try {
final ItemStack stack = te.getItemFrameStack();
if(stack.isEmpty()) return;
final int di = Mth.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(EdLabeledCrate.LabeledCrateBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
double ox = tr[di][0], oy = tr[di][1], oz = tr[di][2];
float ry = (float)tr[di][3];
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.YP.rotationDegrees(ry));
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, ItemTransforms.TransformType.FIXED, i5, overlayTexture, mxs, buf, 0);
mxs.popPose();
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled (because broken), exception was: " + e.getMessage());
}
}
}
}
}

View file

@ -1,178 +1,182 @@
/*
* @file TreeCutting.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple tree cutting algorithm.
*/
package wile.engineersdecor.detail;
import net.minecraft.block.*;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.ModEngineersDecor;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3i;
import net.minecraft.world.World;
import com.google.common.collect.ImmutableList;
import java.util.*;
public class TreeCutting
{
private static org.apache.logging.log4j.Logger LOGGER = ModEngineersDecor.logger();
public static boolean canChop(BlockState state)
{ return isLog(state); }
// -------------------------------------------------------------------------------------------------------------------
private static final List<Vector3i> hoffsets = ImmutableList.of(
new Vector3i( 1,0, 0), new Vector3i( 1,0, 1), new Vector3i( 0,0, 1),
new Vector3i(-1,0, 1), new Vector3i(-1,0, 0), new Vector3i(-1,0,-1),
new Vector3i( 0,0,-1), new Vector3i( 1,0,-1)
);
private static boolean isLog(BlockState state)
{ return (state.getBlock().is(BlockTags.LOGS)) || (state.getBlock().getTags().contains(new ResourceLocation("minecraft","logs"))); }
private static boolean isSameLog(BlockState a, BlockState b)
{ return (a.getBlock()==b.getBlock()); }
private static boolean isLeaves(BlockState state)
{
if(state.getBlock() instanceof LeavesBlock) return true;
if(state.getBlock().getTags().contains(new ResourceLocation("minecraft","leaves"))) return true;
return false;
}
private static List<BlockPos> findBlocksAround(final World world, final BlockPos centerPos, final BlockState leaf_type_state, final Set<BlockPos> checked, int recursion_left)
{
ArrayList<BlockPos> to_decay = new ArrayList<BlockPos>();
for(int y=-1; y<=1; ++y) {
final BlockPos layer = centerPos.offset(0,y,0);
for(Vector3i v:hoffsets) {
BlockPos pos = layer.offset(v);
if((!checked.contains(pos)) && (world.getBlockState(pos).getBlock()==leaf_type_state.getBlock())) {
checked.add(pos);
to_decay.add(pos);
if(recursion_left > 0) {
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, recursion_left-1));
}
}
}
}
return to_decay;
}
private static void breakBlock(World world, BlockPos pos)
{
Block.dropResources(world.getBlockState(pos), world, pos);
world.setBlock(pos, world.getFluidState(pos).createLegacyBlock(), 1|2|8);
}
public static int chopTree(World world, BlockState broken_state, BlockPos startPos, int max_blocks_to_break, boolean without_target_block)
{
if(world.isClientSide || !isLog(broken_state)) return 0;
final long ymin = startPos.getY();
final long max_leaf_distance = 8;
Set<BlockPos> checked = new HashSet<BlockPos>();
ArrayList<BlockPos> to_break = new ArrayList<BlockPos>();
ArrayList<BlockPos> to_decay = new ArrayList<BlockPos>();
checked.add(startPos);
// Initial simple layer-up search of same logs. This forms the base corpus, and only leaves and
// leaf-enclosed logs attached to this corpus may be broken/decayed.
{
LinkedList<BlockPos> queue = new LinkedList<BlockPos>();
LinkedList<BlockPos> upqueue = new LinkedList<BlockPos>();
queue.add(startPos);
int cutlevel = 0;
int steps_left = 128;
while(!queue.isEmpty() && (--steps_left >= 0)) {
final BlockPos pos = queue.removeFirst();
// Vertical search
final BlockPos uppos = pos.above();
final BlockState upstate = world.getBlockState(uppos);
if(!checked.contains(uppos)) {
checked.add(uppos);
if(isSameLog(upstate, broken_state)) {
// Up is log
upqueue.add(uppos);
to_break.add(uppos);
steps_left = 128;
} else {
boolean isleaf = isLeaves(upstate);
if(isleaf || world.isEmptyBlock(uppos) || (upstate.getBlock() instanceof VineBlock)) {
if(isleaf) to_decay.add(uppos);
// Up is air, check adjacent for diagonal up (e.g. Accacia)
for(Vector3i v:hoffsets) {
final BlockPos p = uppos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
to_decay.add(p);
}
}
}
}
}
// Lateral search
for(Vector3i v:hoffsets) {
final BlockPos p = pos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
if(p.distSqr(new BlockPos(startPos.getX(), p.getY(), startPos.getZ())) > (cutlevel > 2 ? 256 : 9)) continue;
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
queue.add(p);
to_decay.add(p);
}
}
if(queue.isEmpty() && (!upqueue.isEmpty())) {
queue = upqueue;
upqueue = new LinkedList<BlockPos>();
++cutlevel;
}
}
}
{
// Determine lose logs between the leafs
for(BlockPos pos:to_decay) {
int dist = 1;
to_break.addAll(findBlocksAround(world, pos, broken_state, checked, dist));
}
}
if(!to_decay.isEmpty()) {
final BlockState leaf_type_state = world.getBlockState(to_decay.get(0));
final ArrayList<BlockPos> leafs = to_decay;
to_decay = new ArrayList<BlockPos>();
for(BlockPos pos:leafs) {
int dist = 3;
to_decay.add(pos);
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, dist));
}
}
if(without_target_block) {
checked.remove(startPos);
} else {
to_break.add(startPos);
}
for(BlockPos pos:to_break) breakBlock(world, pos);
for(BlockPos pos:to_decay) breakBlock(world, pos);
{
// And now the bill.
return MathHelper.clamp(((to_break.size()*6/5)+(to_decay.size()/10)-1), 1, 65535);
}
}
}
/*
* @file TreeCutting.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple tree cutting algorithm.
*/
package wile.engineersdecor.detail;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.VineBlock;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.ModEngineersDecor;
import java.util.*;
public class TreeCutting
{
private static final org.apache.logging.log4j.Logger LOGGER = ModEngineersDecor.logger();
public static boolean canChop(BlockState state)
{ return isLog(state); }
// -------------------------------------------------------------------------------------------------------------------
private static final List<Vec3i> hoffsets = ImmutableList.of(
new Vec3i( 1,0, 0), new Vec3i( 1,0, 1), new Vec3i( 0,0, 1),
new Vec3i(-1,0, 1), new Vec3i(-1,0, 0), new Vec3i(-1,0,-1),
new Vec3i( 0,0,-1), new Vec3i( 1,0,-1)
);
private static boolean isLog(BlockState state)
{ return (state.is(BlockTags.LOGS)) || (state.getBlock().getTags().contains(new ResourceLocation("minecraft","logs"))); }
private static boolean isSameLog(BlockState a, BlockState b)
{ return (a.getBlock()==b.getBlock()); }
private static boolean isLeaves(BlockState state)
{
if(state.getBlock() instanceof LeavesBlock) return true;
if(state.getBlock().getTags().contains(new ResourceLocation("minecraft","leaves"))) return true;
return false;
}
private static List<BlockPos> findBlocksAround(final Level world, final BlockPos centerPos, final BlockState leaf_type_state, final Set<BlockPos> checked, int recursion_left)
{
ArrayList<BlockPos> to_decay = new ArrayList<>();
for(int y=-1; y<=1; ++y) {
final BlockPos layer = centerPos.offset(0,y,0);
for(Vec3i v:hoffsets) {
BlockPos pos = layer.offset(v);
if((!checked.contains(pos)) && (world.getBlockState(pos).getBlock()==leaf_type_state.getBlock())) {
checked.add(pos);
to_decay.add(pos);
if(recursion_left > 0) {
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, recursion_left-1));
}
}
}
}
return to_decay;
}
private static void breakBlock(Level world, BlockPos pos)
{
Block.dropResources(world.getBlockState(pos), world, pos);
world.setBlock(pos, world.getFluidState(pos).createLegacyBlock(), 1|2|8);
}
public static int chopTree(Level world, BlockState broken_state, BlockPos startPos, int max_blocks_to_break, boolean without_target_block)
{
if(world.isClientSide || !isLog(broken_state)) return 0;
final long ymin = startPos.getY();
final long max_leaf_distance = 8;
Set<BlockPos> checked = new HashSet<>();
ArrayList<BlockPos> to_break = new ArrayList<>();
ArrayList<BlockPos> to_decay = new ArrayList<>();
checked.add(startPos);
// Initial simple layer-up search of same logs. This forms the base corpus, and only leaves and
// leaf-enclosed logs attached to this corpus may be broken/decayed.
{
LinkedList<BlockPos> queue = new LinkedList<>();
LinkedList<BlockPos> upqueue = new LinkedList<>();
queue.add(startPos);
int cutlevel = 0;
int steps_left = 128;
while(!queue.isEmpty() && (--steps_left >= 0)) {
final BlockPos pos = queue.removeFirst();
// Vertical search
final BlockPos uppos = pos.above();
final BlockState upstate = world.getBlockState(uppos);
if(!checked.contains(uppos)) {
checked.add(uppos);
if(isSameLog(upstate, broken_state)) {
// Up is log
upqueue.add(uppos);
to_break.add(uppos);
steps_left = 128;
} else {
boolean isleaf = isLeaves(upstate);
if(isleaf || world.isEmptyBlock(uppos) || (upstate.getBlock() instanceof VineBlock)) {
if(isleaf) to_decay.add(uppos);
// Up is air, check adjacent for diagonal up (e.g. Accacia)
for(Vec3i v:hoffsets) {
final BlockPos p = uppos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
to_decay.add(p);
}
}
}
}
}
// Lateral search
for(Vec3i v:hoffsets) {
final BlockPos p = pos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
if(p.distSqr(new BlockPos(startPos.getX(), p.getY(), startPos.getZ())) > (cutlevel > 2 ? 256 : 9)) continue;
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
queue.add(p);
to_decay.add(p);
}
}
if(queue.isEmpty() && (!upqueue.isEmpty())) {
queue = upqueue;
upqueue = new LinkedList<>();
++cutlevel;
}
}
}
{
// Determine lose logs between the leafs
for(BlockPos pos:to_decay) {
int dist = 1;
to_break.addAll(findBlocksAround(world, pos, broken_state, checked, dist));
}
}
if(!to_decay.isEmpty()) {
final BlockState leaf_type_state = world.getBlockState(to_decay.get(0));
final ArrayList<BlockPos> leafs = to_decay;
to_decay = new ArrayList<>();
for(BlockPos pos:leafs) {
int dist = 3;
to_decay.add(pos);
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, dist));
}
}
if(without_target_block) {
checked.remove(startPos);
} else {
to_break.add(startPos);
}
for(BlockPos pos:to_break) breakBlock(world, pos);
for(BlockPos pos:to_decay) breakBlock(world, pos);
{
// And now the bill.
return Mth.clamp(((to_break.size()*6/5)+(to_decay.size()/10)-1), 1, 65535);
}
}
}

View file

@ -1,93 +1,95 @@
/*
* @file JEIPlugin.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* JEI plugin (see https://github.com/mezz/JustEnoughItems/wiki/Creating-Plugins)
*/
package wile.engineersdecor.eapi.jei;
//public class JEIPlugin {}
import mezz.jei.api.registration.IRecipeCatalystRegistration;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.blocks.EdCraftingTable;
import mezz.jei.api.constants.VanillaRecipeCategoryUid;
import mezz.jei.api.registration.IRecipeTransferRegistration;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableTileEntity;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@mezz.jei.api.JeiPlugin
public class JEIPlugin implements mezz.jei.api.IModPlugin
{
@Override
public ResourceLocation getPluginUid()
{ return new ResourceLocation(ModEngineersDecor.MODID, "jei_plugin_uid"); }
@Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
try {
registration.addRecipeTransferHandler(
EdCraftingTable.CraftingTableUiContainer.class,
VanillaRecipeCategoryUid.CRAFTING,
1, 9, 10, 36+CraftingTableTileEntity.NUM_OF_STORAGE_SLOTS
);
} catch(Throwable e) {
ModEngineersDecor.logger().warn("Exception in JEI crafting table handler registration: '" + e.getMessage() + "'.");
}
}
}
@Override
public void onRuntimeAvailable(IJeiRuntime jeiRuntime)
{
HashSet<Item> blacklisted = new HashSet<>();
for(Block e: ModContent.getRegisteredBlocks()) {
if(ModConfig.isOptedOut(e) && (e.asItem().getRegistryName().getPath()).equals((e.getRegistryName().getPath()))) {
blacklisted.add(e.asItem());
}
}
for(Item e: ModContent.getRegisteredItems()) {
if(ModConfig.isOptedOut(e) && (!(e instanceof BlockItem))) {
blacklisted.add(e);
}
}
if(!blacklisted.isEmpty()) {
List<ItemStack> blacklist = blacklisted.stream().map(ItemStack::new).collect(Collectors.toList());
try {
jeiRuntime.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM, blacklist);
} catch(Exception e) {
ModEngineersDecor.logger().warn("Exception in JEI opt-out processing: '" + e.getMessage() + "', skipping further JEI optout processing.");
}
}
}
@Override
public void registerRecipeCatalysts(IRecipeCatalystRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.CRAFTING_TABLE), VanillaRecipeCategoryUid.CRAFTING);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_LAB_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_LAB_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_ELECTRICAL_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_ELECTRICAL_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
}
}
/*
* @file JEIPlugin.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* JEI plugin (see https://github.com/mezz/JustEnoughItems/wiki/Creating-Plugins)
*/
package wile.engineersdecor.eapi.jei;
public class JEIPlugin {}
/*
import mezz.jei.api.registration.IRecipeCatalystRegistration;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.blocks.EdCraftingTable;
import mezz.jei.api.constants.VanillaRecipeCategoryUid;
import mezz.jei.api.registration.IRecipeTransferRegistration;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableTileEntity;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@mezz.jei.api.JeiPlugin
public class JEIPlugin implements mezz.jei.api.IModPlugin
{
@Override
public ResourceLocation getPluginUid()
{ return new ResourceLocation(Auxiliaries.modid(), "jei_plugin_uid"); }
@Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
try {
registration.addRecipeTransferHandler(
EdCraftingTable.CraftingTableUiContainer.class,
VanillaRecipeCategoryUid.CRAFTING,
1, 9, 10, 36+CraftingTableTileEntity.NUM_OF_STORAGE_SLOTS
);
} catch(Throwable e) {
ModEngineersDecor.logger().warn("Exception in JEI crafting table handler registration: '" + e.getMessage() + "'.");
}
}
}
@Override
public void onRuntimeAvailable(IJeiRuntime jeiRuntime)
{
HashSet<Item> blacklisted = new HashSet<>();
for(Block e: ModContent.getRegisteredBlocks()) {
if(ModConfig.isOptedOut(e) && (e.asItem().getRegistryName().getPath()).equals((e.getRegistryName().getPath()))) {
blacklisted.add(e.asItem());
}
}
for(Item e: ModContent.getRegisteredItems()) {
if(ModConfig.isOptedOut(e) && (!(e instanceof BlockItem))) {
blacklisted.add(e);
}
}
if(!blacklisted.isEmpty()) {
List<ItemStack> blacklist = blacklisted.stream().map(ItemStack::new).collect(Collectors.toList());
try {
jeiRuntime.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM, blacklist);
} catch(Exception e) {
ModEngineersDecor.logger().warn("Exception in JEI opt-out processing: '" + e.getMessage() + "', skipping further JEI optout processing.");
}
}
}
@Override
public void registerRecipeCatalysts(IRecipeCatalystRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.CRAFTING_TABLE), VanillaRecipeCategoryUid.CRAFTING);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_LAB_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_LAB_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_ELECTRICAL_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_ELECTRICAL_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
}
}
*/

View file

@ -1,46 +1,46 @@
/*
* @file EdItem.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basic item functionality for mod items.
*/
package wile.engineersdecor.items;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class EdItem extends Item
{
public static final Collection<ItemGroup> ENABLED_TABS = Collections.singletonList(ModEngineersDecor.ITEMGROUP);
public static final Collection<ItemGroup> DISABLED_TABS = new ArrayList<ItemGroup>();
public EdItem(Item.Properties properties)
{ super(properties.tab(ModEngineersDecor.ITEMGROUP)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable World world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public Collection<ItemGroup> getCreativeTabs()
{ return ModConfig.isOptedOut(this) ? (DISABLED_TABS) : (ENABLED_TABS); }
}
/*
* @file EdItem.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basic item functionality for mod items.
*/
package wile.engineersdecor.items;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class EdItem extends Item
{
public static final Collection<CreativeModeTab> ENABLED_TABS = Collections.singletonList(ModEngineersDecor.ITEMGROUP);
public static final Collection<CreativeModeTab> DISABLED_TABS = new ArrayList<CreativeModeTab>();
public EdItem(Item.Properties properties)
{ super(properties.tab(ModEngineersDecor.ITEMGROUP)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public Collection<CreativeModeTab> getCreativeTabs()
{ return ModConfig.isOptedOut(this) ? (DISABLED_TABS) : (ENABLED_TABS); }
}

View file

@ -1,214 +1,218 @@
/*
* @file BlockDecorHalfSlab.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.world.World;
import net.minecraft.world.IWorld;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.state.IntegerProperty;
import net.minecraft.entity.EntityType;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.item.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock.RenderTypeHint;
public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14);
protected static final VoxelShape AABBs[] = {
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 2./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 4./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 6./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 8./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 10./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 12./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 14./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 2./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 4./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 6./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 8./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 10./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 12./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 14./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0,0,0,1,1,1)) // <- with 4bit fill
};
protected static final int num_slabs_contained_in_parts_[] = { 1,2,3,4,5,6,7,8,7,6,5,4,3,2,1 ,0x1 }; // <- with 4bit fill
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
public SlabSliceBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
protected boolean is_cube(BlockState state)
{ return state.getValue(PARTS) == 0x07; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip);
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs[state.getValue(PARTS) & 0xf]; }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PARTS); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
final BlockPos pos = context.getClickedPos();
BlockState state = context.getLevel().getBlockState(pos);
if(state.getBlock() == this) {
int parts = state.getValue(PARTS);
if(parts == 7) return null; // -> is already a full block.
parts += (parts < 7) ? 1 : -1;
if(parts==7) state = state.setValue(WATERLOGGED, false);
return state.setValue(PARTS, parts);
} else {
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context); // fluid state
if(face == Direction.UP) return placement_state.setValue(PARTS, 0);
if(face == Direction.DOWN) return placement_state.setValue(PARTS, 14);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(PARTS, isupper ? 14 : 0);
}
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final int parts = state.getValue(PARTS);
if(parts == 7) return false;
if((face == Direction.UP) && (parts < 7)) return true;
if((face == Direction.DOWN) && (parts > 7)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (parts==0) : (parts==1);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return new ArrayList<ItemStack>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, World world, BlockPos pos, PlayerEntity player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vector3d lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
int parts = state.getValue(PARTS);
if((facing == Direction.DOWN) && (parts <= 7)) {
if(parts > 0) {
world.setBlock(pos, state.setValue(PARTS, parts-1), 3);
} else {
world.removeBlock(pos, false);
}
} else if((facing == Direction.UP) && (parts >= 7)) {
if(parts < 14) {
world.setBlock(pos, state.setValue(PARTS, parts + 1), 3);
} else {
world.removeBlock(pos, false);
}
} else {
return;
}
if(!player.isCreative()) {
stack.grow(1);
if(player.inventory != null) player.inventory.setChanged(); // @todo: check if inventory can actually be null
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundCategory.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(IWorld world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(PARTS)==14) ? false : super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(IBlockReader world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(PARTS)==14) ? false : super.canPlaceLiquid(world, pos, state, fluid); }
}
/*
* @file BlockDecorHalfSlab.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14);
protected static final VoxelShape[] AABBs = {
Shapes.create(new AABB(0, 0./16, 0, 1, 2./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 4./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 6./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 8./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 10./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 12./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 14./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 2./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 4./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 6./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 8./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 10./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 12./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 14./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0,0,0,1,1,1)) // <- with 4bit fill
};
protected static final int[] num_slabs_contained_in_parts_ = { 1,2,3,4,5,6,7,8,7,6,5,4,3,2,1 ,0x1 }; // <- with 4bit fill
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
public SlabSliceBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); }
protected boolean is_cube(BlockState state)
{ return state.getValue(PARTS) == 0x07; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip);
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs[state.getValue(PARTS) & 0xf]; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PARTS); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
final BlockPos pos = context.getClickedPos();
BlockState state = context.getLevel().getBlockState(pos);
if(state.getBlock() == this) {
int parts = state.getValue(PARTS);
if(parts == 7) return null; // -> is already a full block.
parts += (parts < 7) ? 1 : -1;
if(parts==7) state = state.setValue(WATERLOGGED, false);
return state.setValue(PARTS, parts);
} else {
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context); // fluid state
if(face == Direction.UP) return placement_state.setValue(PARTS, 0);
if(face == Direction.DOWN) return placement_state.setValue(PARTS, 14);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(PARTS, isupper ? 14 : 0);
}
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final int parts = state.getValue(PARTS);
if(parts == 7) return false;
if((face == Direction.UP) && (parts < 7)) return true;
if((face == Direction.DOWN) && (parts > 7)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (parts==0) : (parts==1);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, Level world, BlockPos pos, Player player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vec3 lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
int parts = state.getValue(PARTS);
if((facing == Direction.DOWN) && (parts <= 7)) {
if(parts > 0) {
world.setBlock(pos, state.setValue(PARTS, parts-1), 3);
} else {
world.removeBlock(pos, false);
}
} else if((facing == Direction.UP) && (parts >= 7)) {
if(parts < 14) {
world.setBlock(pos, state.setValue(PARTS, parts + 1), 3);
} else {
world.removeBlock(pos, false);
}
} else {
return;
}
if(!player.isCreative()) {
stack.grow(1);
if(player.getInventory() != null) player.getInventory().setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); }
@Override
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); }
}

View file

@ -1,162 +1,168 @@
/*
* @file StandardDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Door blocks, almost entirely based on vanilla.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.state.properties.DoorHingeSide;
import net.minecraft.state.properties.DoubleBlockHalf;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock
{
private final long config_;
protected final VoxelShape shapes_[][][][];
protected final SoundEvent open_sound_;
protected final SoundEvent close_sound_;
public StandardDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] open_aabbs_top, AxisAlignedBB[] open_aabbs_bottom, AxisAlignedBB[] closed_aabbs_top, AxisAlignedBB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{
super(properties);
VoxelShape shapes[][][][] = new VoxelShape[Direction.values().length][2][2][2];
for(Direction facing: Direction.values()) {
for(boolean open: new boolean[]{false,true}) {
for(DoubleBlockHalf half: new DoubleBlockHalf[]{DoubleBlockHalf.UPPER,DoubleBlockHalf.LOWER}) {
for(boolean hinge_right: new boolean[]{false,true}) {
VoxelShape shape = VoxelShapes.empty();
if(facing.getAxis() == Axis.Y) {
shape = VoxelShapes.block();
} else {
final AxisAlignedBB[] aabbs = (open)?((half==DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half==DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom);
for(AxisAlignedBB e:aabbs) {
AxisAlignedBB aabb = Auxiliaries.getRotatedAABB(e, facing, true);
if(!hinge_right) aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis());
shape = VoxelShapes.join(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
}
}
shapes[facing.ordinal()][open?1:0][hinge_right?1:0][half==DoubleBlockHalf.UPPER?0:1] = shape;
}
}
}
}
config_ = config;
shapes_ = shapes;
open_sound_ = open_sound;
close_sound_ = close_sound;
}
public StandardDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB open_aabb, AxisAlignedBB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ this(config, properties, new AxisAlignedBB[]{open_aabb}, new AxisAlignedBB[]{open_aabb}, new AxisAlignedBB[]{closed_aabb}, new AxisAlignedBB[]{closed_aabb}, open_sound, close_sound); }
public StandardDoorBlock(long config, AbstractBlock.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
open_sound,
close_sound
);
}
public StandardDoorBlock(long config, AbstractBlock.Properties properties)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
SoundEvents.WOODEN_DOOR_OPEN,
SoundEvents.WOODEN_DOOR_CLOSE
);
}
@Override
public long config()
{ return config_; }
protected void sound(IBlockReader world, BlockPos pos, boolean open)
{ if(world instanceof World) ((World)world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundCategory.BLOCKS, 0.7f, 1f); }
protected void actuate_adjacent_wing(BlockState state, IBlockReader world_ro, BlockPos pos, boolean open)
{
if(!(world_ro instanceof World)) return;
final World world = (World)world_ro;
final BlockPos adjecent_pos = pos.relative( (state.getValue(HINGE)==DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise()));
if(!world.isLoaded(adjecent_pos)) return;
BlockState adjacent_state = world.getBlockState(adjecent_pos);
if(adjacent_state.getBlock()!=this) return;
if(adjacent_state.getValue(OPEN)==open) return;
world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2|10);
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext context)
{ return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN)?1:0][state.getValue(HINGE)==DoorHingeSide.RIGHT?1:0][state.getValue(HALF)==DoubleBlockHalf.UPPER?0:1]; }
@Override
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{ setOpen(world, state, pos, !state.getValue(OPEN)); return ActionResultType.sidedSuccess(world.isClientSide()); }
@Override
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
if((block == this) || (powered == state.getValue(POWERED))) return;
world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2);
actuate_adjacent_wing(state, world, pos, powered);
if(powered != state.getValue(OPEN)) sound(world, pos, powered);
}
@Override
public void setOpen(World world, BlockState state, BlockPos pos, boolean open)
{
if(!state.is(this) || (state.getValue(OPEN) == open)) return;
state = state.setValue(OPEN, open);
world.setBlock(pos, state, 2|8);
sound(world, pos, open);
actuate_adjacent_wing(state, world, pos, open);
}
}
/*
* @file StandardDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Door blocks, almost entirely based on vanilla.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoorBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock
{
private final long config_;
protected final VoxelShape[][][][] shapes_;
protected final SoundEvent open_sound_;
protected final SoundEvent close_sound_;
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{
super(properties);
VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2];
for(Direction facing: Direction.values()) {
for(boolean open: new boolean[]{false,true}) {
for(DoubleBlockHalf half: new DoubleBlockHalf[]{DoubleBlockHalf.UPPER,DoubleBlockHalf.LOWER}) {
for(boolean hinge_right: new boolean[]{false,true}) {
VoxelShape shape = Shapes.empty();
if(facing.getAxis() == Direction.Axis.Y) {
shape = Shapes.block();
} else {
final AABB[] aabbs = (open)?((half==DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half==DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom);
for(AABB e:aabbs) {
AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true);
if(!hinge_right) aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis());
shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR);
}
}
shapes[facing.ordinal()][open?1:0][hinge_right?1:0][half==DoubleBlockHalf.UPPER?0:1] = shape;
}
}
}
}
config_ = config;
shapes_ = shapes;
open_sound_ = open_sound;
close_sound_ = close_sound;
}
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound); }
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
open_sound,
close_sound
);
}
public StandardDoorBlock(long config, BlockBehaviour.Properties properties)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
SoundEvents.WOODEN_DOOR_OPEN,
SoundEvents.WOODEN_DOOR_CLOSE
);
}
@Override
public long config()
{ return config_; }
protected void sound(BlockGetter world, BlockPos pos, boolean open)
{ if(world instanceof Level) ((Level)world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); }
protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open)
{
if(!(world_ro instanceof final Level world)) return;
final BlockPos adjecent_pos = pos.relative( (state.getValue(HINGE)==DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise()));
if(!world.isLoaded(adjecent_pos)) return;
BlockState adjacent_state = world.getBlockState(adjecent_pos);
if(adjacent_state.getBlock()!=this) return;
if(adjacent_state.getValue(OPEN)==open) return;
world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2|10);
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context)
{ return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN)?1:0][state.getValue(HINGE)==DoorHingeSide.RIGHT?1:0][state.getValue(HALF)==DoubleBlockHalf.UPPER?0:1]; }
@Override
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{ setOpen(player, world, state, pos, !state.getValue(OPEN)); return InteractionResult.sidedSuccess(world.isClientSide()); }
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
if((block == this) || (powered == state.getValue(POWERED))) return;
world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2);
actuate_adjacent_wing(state, world, pos, powered);
if(powered != state.getValue(OPEN)) sound(world, pos, powered);
}
@Override
public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open)
{
if(!state.is(this) || (state.getValue(OPEN) == open)) return;
state = state.setValue(OPEN, open);
world.setBlock(pos, state, 2|8);
sound(world, pos, open);
actuate_adjacent_wing(state, world, pos, open);
}
}

View file

@ -0,0 +1,68 @@
/*
* @file StandardEntityBlocks.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for blocks with block entities.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraftforge.common.util.FakePlayer;
import javax.annotation.Nullable;
public class StandardEntityBlocks
{
public interface IStandardEntityBlock<ET extends StandardBlockEntity> extends EntityBlock
{
@Nullable
BlockEntityType<ET> getBlockEntityType();
@Override
@Nullable
default BlockEntity newBlockEntity(BlockPos pos, BlockState state)
{ return (getBlockEntityType()==null) ? null : getBlockEntityType().create(pos, state); }
@Override
@Nullable
default <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockState state, BlockEntityType<T> te_type)
{ return (world.isClientSide) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity)te).tick()); } // To be evaluated if
@Override
@Nullable
default <T extends BlockEntity> GameEventListener getListener(Level world, T te)
{ return null; }
default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL;
player.openMenu((MenuProvider)te);
return InteractionResult.CONSUME;
}
}
public static abstract class StandardBlockEntity extends BlockEntity
{
public StandardBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state)
{ super(type, pos, state); }
public void tick()
{}
}
}

View file

@ -1,194 +1,200 @@
/*
* @file StandardFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.*;
import net.minecraft.fluid.FluidState;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import javax.annotation.Nullable;
import java.util.*;
public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallHeight> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallHeight> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallHeight> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallHeight> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public StandardFenceBlock(long config, AbstractBlock.Properties properties)
{ this(config, properties, 1.5,16, 1.5, 0, 14, 16); }
public StandardFenceBlock(long config, AbstractBlock.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
super(properties);
shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y);
collision_shape_voxels = buildShapes(pole_width,24, pole_width, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallHeight height, VoxelShape low, VoxelShape high)
{
if(height == WallHeight.TALL) return VoxelShapes.or(pole, high);
if(height == WallHeight.LOW) return VoxelShapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallHeight wh_east : WALL_EAST.getPossibleValues()) {
for(WallHeight wh_north : WALL_NORTH.getPossibleValues()) {
for(WallHeight wh_west : WALL_WEST.getPossibleValues()) {
for(WallHeight wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = VoxelShapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = VoxelShapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
builder.put(bs.setValue(WATERLOGGED, false), shape);
builder.put(bs.setValue(WATERLOGGED, true), shape);
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side);
}
protected WallHeight selectWallHeight(IWorldReader world, BlockPos pos, Direction direction)
{
return WallHeight.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockItemUseContext context)
{
IWorldReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState()
.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(WATERLOGGED, fs.getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(BlockStateProperties.WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH)!=WallHeight.NONE);
boolean e = (side==Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) !=WallHeight.NONE);
boolean s = (side==Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH)!=WallHeight.NONE);
boolean w = (side==Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) !=WallHeight.NONE);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE);
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file StandardFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.WallSide;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallSide> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallSide> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallSide> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallSide> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public StandardFenceBlock(long config, BlockBehaviour.Properties properties)
{ this(config, properties, 1.5,16, 1.5, 0, 14, 16); }
public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
super(properties);
shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y);
collision_shape_voxels = buildShapes(pole_width,24, pole_width, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high)
{
if(height == WallSide.TALL) return Shapes.or(pole, high);
if(height == WallSide.LOW) return Shapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
ImmutableMap.Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallSide wh_east : WALL_EAST.getPossibleValues()) {
for(WallSide wh_north : WALL_NORTH.getPossibleValues()) {
for(WallSide wh_west : WALL_WEST.getPossibleValues()) {
for(WallSide wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = Shapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = Shapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
builder.put(bs.setValue(WATERLOGGED, false), shape);
builder.put(bs.setValue(WATERLOGGED, true), shape);
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side);
}
protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction)
{
return WallSide.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockPlaceContext context)
{
LevelReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState()
.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(WATERLOGGED, fs.getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(BlockStateProperties.WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH)!=WallSide.NONE);
boolean e = (side==Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) !=WallSide.NONE);
boolean s = (side==Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH)!=WallSide.NONE);
boolean w = (side==Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) !=WallSide.NONE);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE);
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,55 +1,57 @@
/*
* @file StandardStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
public class StandardStairsBlock extends StairsBlock implements StandardBlocks.IStandardBlock
{
private final long config;
public StandardStairsBlock(long config, BlockState state, AbstractBlock.Properties properties)
{ super(()->state, properties); this.config = config; }
public StandardStairsBlock(long config, java.util.function.Supplier<BlockState> state, AbstractBlock.Properties properties)
{ super(state, properties); this.config = config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file StandardStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.StairBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock
{
private final long config;
public StandardStairsBlock(long config, BlockState state, BlockBehaviour.Properties properties)
{ super(()->state, properties); this.config = config; }
public StandardStairsBlock(long config, java.util.function.Supplier<BlockState> state, BlockBehaviour.Properties properties)
{ super(state, properties); this.config = config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,201 +1,206 @@
/*
* @file VariantSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.SlabType;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock.RenderTypeHint;
public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final EnumProperty<SlabType> TYPE = BlockStateProperties.SLAB_TYPE;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3);
protected static final VoxelShape AABBs[] = {
VoxelShapes.create(new AxisAlignedBB(0, 8./16, 0, 1, 16./16, 1)), // top slab
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 8./16, 1)), // bottom slab
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)), // both slabs
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)) // << 2bit fill
};
protected static final int num_slabs_contained_in_parts_[] = {1,1,2,2};
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
protected boolean is_cube(BlockState state)
{ return state.getValue(TYPE) == SlabType.DOUBLE; }
public VariantSlabBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true);
}
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState==state) ? true : super.skipRendering(state, adjacentBlockState, side); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs[state.getValue(TYPE).ordinal() & 0x3]; }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TYPE, TEXTURE_VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockPos pos = context.getClickedPos();
if(context.getLevel().getBlockState(pos).getBlock() == this) return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false);
final int rnd = MathHelper.clamp((int)(MathHelper.getSeed(context.getClickedPos()) & 0x3), 0, 3);
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state
if(face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM);
if(face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM);
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final SlabType type = state.getValue(TYPE);
if((face == Direction.UP) && (type==SlabType.BOTTOM)) return true;
if((face == Direction.DOWN) && (type==SlabType.TOP)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (type==SlabType.BOTTOM) : (type==SlabType.TOP);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return new ArrayList<ItemStack>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, World world, BlockPos pos, PlayerEntity player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vector3d lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
SlabType type = state.getValue(TYPE);
if(facing == Direction.DOWN) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3);
} else {
world.removeBlock(pos, false);
}
} else if(facing == Direction.UP) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3);
} else {
world.removeBlock(pos, false);
}
}
if(!player.isCreative()) {
stack.grow(1);
if(player.inventory != null) player.inventory.setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundCategory.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(IWorld world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(TYPE)==SlabType.DOUBLE) ? false : super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(IBlockReader world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(TYPE)==SlabType.DOUBLE) ? false : super.canPlaceLiquid(world, pos, state, fluid); }
}
/*
* @file VariantSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final EnumProperty<SlabType> TYPE = BlockStateProperties.SLAB_TYPE;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3);
protected static final VoxelShape[] AABBs = {
Shapes.create(new AABB(0, 8./16, 0, 1, 16./16, 1)), // top slab
Shapes.create(new AABB(0, 0./16, 0, 1, 8./16, 1)), // bottom slab
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)), // both slabs
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)) // << 2bit fill
};
protected static final int[] num_slabs_contained_in_parts_ = {1,1,2,2};
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
protected boolean is_cube(BlockState state)
{ return state.getValue(TYPE) == SlabType.DOUBLE; }
public VariantSlabBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true);
}
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState==state) || (super.skipRendering(state, adjacentBlockState, side)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs[state.getValue(TYPE).ordinal() & 0x3]; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TYPE, TEXTURE_VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockPos pos = context.getClickedPos();
if(context.getLevel().getBlockState(pos).getBlock() == this) return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false);
final int rnd = Mth.clamp((int)(Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3);
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state
if(face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM);
if(face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM);
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final SlabType type = state.getValue(TYPE);
if((face == Direction.UP) && (type==SlabType.BOTTOM)) return true;
if((face == Direction.DOWN) && (type==SlabType.TOP)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (type==SlabType.BOTTOM) : (type==SlabType.TOP);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, Level world, BlockPos pos, Player player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vec3 lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
SlabType type = state.getValue(TYPE);
if(facing == Direction.DOWN) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3);
} else {
world.removeBlock(pos, false);
}
} else if(facing == Direction.UP) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3);
} else {
world.removeBlock(pos, false);
}
}
if(!player.isCreative()) {
stack.grow(1);
if(player.getInventory() != null) player.getInventory().setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); }
}

View file

@ -1,197 +1,198 @@
/*
* @file VariantWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.shapes.VoxelShapes;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.world.*;
import net.minecraft.fluid.FluidState;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.state.StateContainer;
import net.minecraft.util.math.MathHelper;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.state.IntegerProperty;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallHeight> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallHeight> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallHeight> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallHeight> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7);
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public VariantWallBlock(long config, AbstractBlock.Properties builder)
{
super(builder);
shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16);
collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallHeight height, VoxelShape low, VoxelShape high)
{
if(height == WallHeight.TALL) return VoxelShapes.or(pole, high);
if(height == WallHeight.LOW) return VoxelShapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallHeight wh_east : WALL_EAST.getPossibleValues()) {
for(WallHeight wh_north : WALL_NORTH.getPossibleValues()) {
for(WallHeight wh_west : WALL_WEST.getPossibleValues()) {
for(WallHeight wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = VoxelShapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = VoxelShapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
final VoxelShape tvs = shape;
TEXTURE_VARIANT.getPossibleValues().forEach((tv)->{
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs);
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs);
});
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TEXTURE_VARIANT); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof VariantWallBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side);
}
protected WallHeight selectWallHeight(IWorldReader world, BlockPos pos, Direction direction)
{
return WallHeight.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockItemUseContext context)
{
IWorldReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState().setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(WATERLOGGED, fs.getType()==Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH)!=WallHeight.NONE;
boolean e = (side==Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST)!=WallHeight.NONE;
boolean s = (side==Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH)!=WallHeight.NONE;
boolean w = (side==Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST)!=WallHeight.NONE;
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(TEXTURE_VARIANT, ((int)MathHelper.getSeed(pos)) & 0x7);
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file VariantWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.*;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallSide> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallSide> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallSide> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallSide> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7);
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public VariantWallBlock(long config, BlockBehaviour.Properties builder)
{
super(builder);
shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16);
collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high)
{
if(height == WallSide.TALL) return Shapes.or(pole, high);
if(height == WallSide.LOW) return Shapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallSide wh_east : WALL_EAST.getPossibleValues()) {
for(WallSide wh_north : WALL_NORTH.getPossibleValues()) {
for(WallSide wh_west : WALL_WEST.getPossibleValues()) {
for(WallSide wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = Shapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = Shapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
final VoxelShape tvs = shape;
TEXTURE_VARIANT.getPossibleValues().forEach((tv)->{
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs);
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs);
});
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TEXTURE_VARIANT); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof VariantWallBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side);
}
protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction)
{ return WallSide.LOW; }
public BlockState getStateForPlacement(BlockPlaceContext context)
{
LevelReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState().setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(WATERLOGGED, fs.getType()==Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH)!=WallSide.NONE;
boolean e = (side==Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST)!=WallSide.NONE;
boolean s = (side==Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH)!=WallSide.NONE;
boolean w = (side==Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST)!=WallSide.NONE;
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(TEXTURE_VARIANT, ((int)Mth.getSeed(pos)) & 0x7);
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,42 +0,0 @@
package wile.engineersdecor.libmc.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.IGuiEventListener;
import net.minecraft.client.gui.IHasContainer;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public abstract class ContainerGui<T extends Container> extends ContainerScreen<T> implements IHasContainer<T>
{
public ContainerGui(T screenContainer, PlayerInventory inv, ITextComponent titleIn)
{ super(screenContainer, inv, titleIn); }
protected boolean canHaveDisturbingButtonsFromOtherMods()
{ return false; }
public void init(Minecraft minecraft, int width, int height)
{
this.minecraft = minecraft;
this.itemRenderer = minecraft.getItemRenderer();
this.font = minecraft.font;
this.width = width;
this.height = height;
java.util.function.Consumer<Widget> remove = (b) -> { buttons.remove(b); children.remove(b); };
if((!canHaveDisturbingButtonsFromOtherMods()) || (!net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent.Pre(this, this.buttons, this::addButton, remove)))) {
this.buttons.clear();
this.children.clear();
this.setFocused((IGuiEventListener)null);
this.init();
}
if(canHaveDisturbingButtonsFromOtherMods()) {
net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent.Post(this, this.buttons, this::addButton, remove));
}
}
}

View file

@ -1,48 +0,0 @@
/*
* @file BlockStateDataGen.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Blockstate data generator.
*/
package wile.engineersdecor.libmc.datagen;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraft.data.*;
public class AssetsDataGen
{
public static class BlockStates extends BlockStateProvider
{
public BlockStates(DataGenerator gen, ExistingFileHelper efh)
{ super(gen, Auxiliaries.modid(), efh); }
@Override
public String getName()
{ return Auxiliaries.modid() + " Block states"; }
@Override
protected void registerStatesAndModels()
{
}
}
public static class ItemModels extends ItemModelProvider
{
public ItemModels(DataGenerator generator, ExistingFileHelper efh)
{ super(generator, Auxiliaries.modid(), efh); }
@Override
public String getName()
{ return Auxiliaries.modid() + "Item models"; }
@Override
protected void registerModels()
{
}
}
}

View file

@ -1,96 +0,0 @@
/*
* @file LootTableGen.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Loot table generator.
*/
package wile.engineersdecor.libmc.datagen;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.block.Block;
import net.minecraft.data.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.loot.*;
import net.minecraft.loot.functions.CopyName;
import net.minecraft.loot.functions.CopyName.Source;
import net.minecraft.loot.functions.CopyNbt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class LootTableGen extends LootTableProvider
{
private static final Logger LOGGER = LogManager.getLogger();
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
private final DataGenerator generator;
private final Supplier<List<Block>> block_listing;
//--------------------------------------------------------------------------------------------------------------------
public LootTableGen(DataGenerator gen, Supplier<List<Block>> block_list_supplier)
{ super(gen); generator=gen; block_listing = block_list_supplier; }
//-- LootTableProvider -----------------------------------------------------------------------------------------------
@Override
public String getName()
{ return Auxiliaries.modid() + " Loot Tables"; }
@Override
public void run(DirectoryCache cache)
{ save(cache, generate()); }
//--------------------------------------------------------------------------------------------------------------------
private Map<ResourceLocation, LootTable> generate()
{
final HashMap<ResourceLocation, LootTable> tables = new HashMap<ResourceLocation, LootTable>();
final List<Block> blocks = block_listing.get();
blocks.forEach((block)->{
LOGGER.info("Generating loot table for " + block.getRegistryName());
if((!(block instanceof StandardBlocks.IStandardBlock)) || (!(((StandardBlocks.IStandardBlock)block).hasDynamicDropList()))) {
tables.put(
block.getLootTable(),
defaultBlockDrops(block.getRegistryName().getPath() + "_dlt", block)
.setParamSet(LootParameterSets.BLOCK).build());
} else {
LOGGER.info("Dynamic drop list, skipping loot table for " + block.getRegistryName());
}
});
return tables;
}
private void save(DirectoryCache cache, Map<ResourceLocation, LootTable> tables)
{
final Path root = generator.getOutputFolder();
tables.forEach((rl,tab)->{
Path fp = root.resolve("data/" + rl.getNamespace() + "/loot_tables/" + rl.getPath() + ".json");
try {
IDataProvider.save(GSON, cache, LootTableManager.serialize(tab), fp);
} catch(Exception e) {
LOGGER.error("Failed to save loottable '"+fp+"', exception: " + e);
}
});
}
private LootTable.Builder defaultBlockDrops(String rl_path, Block block)
{
StandaloneLootEntry.Builder iltb = ItemLootEntry.lootTableItem(block);
iltb.apply(CopyName.copyName(Source.BLOCK_ENTITY));
if(block.hasTileEntity(block.defaultBlockState())) {
iltb.apply(CopyNbt.copyData(CopyNbt.Source.BLOCK_ENTITY));
}
return LootTable.lootTable().withPool(LootPool.lootPool().name(rl_path).setRolls(ConstantRange.exactly(1)).add(iltb));
}
}

View file

@ -1,391 +1,505 @@
/*
* @file Auxiliaries.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General commonly used functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.util.InputMappings;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.SharedConstants;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.item.ItemStack;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import wile.engineersdecor.ModConfig;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Auxiliaries
{
private static String modid;
private static Logger logger;
private static Supplier<CompoundNBT> server_config_supplier = ()->new CompoundNBT();
public static void init(String modid, Logger logger, Supplier<CompoundNBT> server_config_supplier)
{
Auxiliaries.modid = modid;
Auxiliaries.logger = logger;
Auxiliaries.server_config_supplier = server_config_supplier;
}
// -------------------------------------------------------------------------------------------------------------------
// Mod specific exports
// -------------------------------------------------------------------------------------------------------------------
public static String modid()
{ return modid; }
public static Logger logger()
{ return logger; }
// -------------------------------------------------------------------------------------------------------------------
// Sideness, system/environment, tagging interfaces
// -------------------------------------------------------------------------------------------------------------------
public interface IExperimentalFeature {}
public static final boolean isModLoaded(final String registry_name)
{ return ModList.get().isLoaded(registry_name); }
public static final boolean isDevelopmentMode()
{ return SharedConstants.IS_RUNNING_IN_IDE; }
@OnlyIn(Dist.CLIENT)
public static final boolean isShiftDown()
{
return (InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) ||
InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT));
}
@OnlyIn(Dist.CLIENT)
public static final boolean isCtrlDown()
{
return (InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) ||
InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL));
}
// -------------------------------------------------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------------------------------------------------
public static final void logInfo(final String msg)
{ logger.info(msg); }
public static final void logWarn(final String msg)
{ logger.warn(msg); }
public static final void logError(final String msg)
{ logger.error(msg); }
public static final void logDebug(final String msg)
{ if(ModConfig.withDebug()) logger.warn(msg); }
// -------------------------------------------------------------------------------------------------------------------
// Localization, text formatting
// -------------------------------------------------------------------------------------------------------------------
/**
* Text localisation wrapper, implicitly prepends `MODID` to the
* translation keys. Forces formatting argument, nullable if no special formatting shall be applied..
*/
public static TranslationTextComponent localizable(String modtrkey, Object... args)
{
return new TranslationTextComponent((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid+"."+modtrkey), args);
}
public static TranslationTextComponent localizable(String modtrkey)
{ return localizable(modtrkey, new Object[]{}); }
public static TranslationTextComponent localizable_block_key(String blocksubkey)
{ return new TranslationTextComponent("block."+modid+"."+blocksubkey); }
@OnlyIn(Dist.CLIENT)
public static String localize(String translationKey, Object... args)
{
TranslationTextComponent tr = new TranslationTextComponent(translationKey, args);
tr.withStyle(TextFormatting.RESET);
final String ft = tr.getString();
if(ft.contains("${")) {
// Non-recursive, non-argument lang file entry cross referencing.
Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher mt = pt.matcher(ft);
StringBuffer sb = new StringBuffer();
while(mt.find()) {
String m = mt.group(1);
if(m.contains("?")) {
String[] kv = m.split("\\?", 2);
String key = kv[0].trim();
boolean not = key.startsWith("!");
if(not) key = key.replaceFirst("!", "");
m = kv[1].trim();
if(!server_config_supplier.get().contains(key)) {
m = "";
} else {
boolean r = server_config_supplier.get().getBoolean(key);
if(not) r = !r;
if(!r) m = "";
}
}
mt.appendReplacement(sb, Matcher.quoteReplacement((new TranslationTextComponent(m)).getString().trim()));
}
mt.appendTail(sb);
return sb.toString();
} else {
return ft;
}
}
/**
* Returns true if a given key is translated for the current language.
*/
@OnlyIn(Dist.CLIENT)
public static boolean hasTranslation(String key)
{ return net.minecraft.client.resources.I18n.exists(key); }
public static final class Tooltip
{
@OnlyIn(Dist.CLIENT)
public static boolean extendedTipCondition()
{ return isShiftDown(); }
@OnlyIn(Dist.CLIENT)
public static boolean helpCondition()
{ return isShiftDown() && isCtrlDown(); }
/**
* Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT.
* Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or
* no translation found).
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List<ITextComponent> tooltip, ITooltipFlag flag, boolean addAdvancedTooltipHints)
{
// Note: intentionally not using keybinding here, this must be `control` or `shift`.
final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help");
final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip");
if((!help_available) && (!tip_available)) return false;
String tip_text = "";
if(helpCondition()) {
if(help_available) tip_text = localize(helpTranslationKey + ".help");
} else if(extendedTipCondition()) {
if(tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip");
} else if(addAdvancedTooltipHints) {
if(tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : "");
if(help_available) tip_text += localize(modid + ".tooltip.hint.help");
}
if(tip_text.isEmpty()) return false;
String[] tip_list = tip_text.split("\\r?\\n");
for(String tip:tip_list) {
tooltip.add(new StringTextComponent(tip.replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(TextFormatting.GRAY));
}
return true;
}
/**
* Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT.
* Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help".
* Return value see method pattern above.
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag, boolean addAdvancedTooltipHints)
{ return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); }
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(String translation_key, List<ITextComponent> tooltip)
{
if(!Auxiliaries.hasTranslation(translation_key)) return false;
tooltip.add(new StringTextComponent(localize(translation_key).replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(TextFormatting.GRAY));
return true;
}
}
@SuppressWarnings("unused")
public static void playerChatMessage(final PlayerEntity player, final String message)
{
String s = message.trim();
if(!s.isEmpty()) player.sendMessage(new TranslationTextComponent(s), new UUID(0,0));
}
public static @Nullable ITextComponent unserializeTextComponent(String serialized)
{ return ITextComponent.Serializer.fromJson(serialized); }
public static String serializeTextComponent(ITextComponent tc)
{ return (tc==null) ? ("") : (ITextComponent.Serializer.toJson(tc)); }
// -------------------------------------------------------------------------------------------------------------------
// Item NBT data
// -------------------------------------------------------------------------------------------------------------------
/**
* Equivalent to getDisplayName(), returns null if no custom name is set.
*/
public static @Nullable ITextComponent getItemLabel(ItemStack stack)
{
CompoundNBT nbt = stack.getTagElement("display");
if(nbt != null && nbt.contains("Name", 8)) {
try {
ITextComponent tc = unserializeTextComponent(nbt.getString("Name"));
if(tc != null) return tc;
nbt.remove("Name");
} catch(Exception e) {
nbt.remove("Name");
}
}
return null;
}
public static ItemStack setItemLabel(ItemStack stack, @Nullable ITextComponent name)
{
if(name != null) {
CompoundNBT nbt = stack.getOrCreateTagElement("display");
nbt.putString("Name", serializeTextComponent(name));
} else {
if(stack.hasTag()) stack.removeTagKey("display");
}
return stack;
}
// -------------------------------------------------------------------------------------------------------------------
// Block handling
// -------------------------------------------------------------------------------------------------------------------
public static final AxisAlignedBB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1)
{ return new AxisAlignedBB(x0/16.0, y0/16.0, z0/16.0, x1/16.0, y1/16.0, z1/16.0); }
public static final AxisAlignedBB getRotatedAABB(AxisAlignedBB bb, Direction new_facing, boolean horizontal_rotation)
{
if(!horizontal_rotation) {
switch(new_facing.get3DDataValue()) {
case 0: return new AxisAlignedBB(1-bb.maxX, bb.minZ, bb.minY, 1-bb.minX, bb.maxZ, bb.maxY); // D
case 1: return new AxisAlignedBB(1-bb.maxX, 1-bb.maxZ, 1-bb.maxY, 1-bb.minX, 1-bb.minZ, 1-bb.minY); // U
case 2: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AxisAlignedBB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AxisAlignedBB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AxisAlignedBB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
} else {
switch(new_facing.get3DDataValue()) {
case 0: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb
case 1: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb
case 2: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AxisAlignedBB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AxisAlignedBB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AxisAlignedBB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
}
return bb;
}
public static final AxisAlignedBB[] getRotatedAABB(AxisAlignedBB[] bbs, Direction new_facing, boolean horizontal_rotation)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation);
return transformed;
}
public static final AxisAlignedBB getYRotatedAABB(AxisAlignedBB bb, int clockwise_90deg_steps)
{
final Direction direction_map[] = new Direction[]{Direction.NORTH,Direction.EAST,Direction.SOUTH,Direction.WEST};
return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps+4096) & 0x03], true);
}
public static final AxisAlignedBB[] getYRotatedAABB(AxisAlignedBB[] bbs, int clockwise_90deg_steps)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps);
return transformed;
}
public static final AxisAlignedBB getMirroredAABB(AxisAlignedBB bb, Axis axis)
{
switch(axis) {
case X: return new AxisAlignedBB(1-bb.maxX, bb.minY, bb.minZ, 1-bb.minX, bb.maxY, bb.maxZ);
case Y: return new AxisAlignedBB(bb.minX, 1-bb.maxY, bb.minZ, bb.maxX, 1-bb.minY, bb.maxZ);
case Z: return new AxisAlignedBB(bb.minX, bb.minY, 1-bb.maxZ, bb.maxX, bb.maxY, 1-bb.minZ);
default: return bb;
}
}
public static final AxisAlignedBB[] getMirroredAABB(AxisAlignedBB[] bbs, Axis axis)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis);
return transformed;
}
public static final VoxelShape getUnionShape(AxisAlignedBB ... aabbs)
{
VoxelShape shape = VoxelShapes.empty();
for(AxisAlignedBB aabb: aabbs) shape = VoxelShapes.join(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
return shape;
}
public static final VoxelShape getUnionShape(AxisAlignedBB[] ... aabb_list)
{
VoxelShape shape = VoxelShapes.empty();
for(AxisAlignedBB[] aabbs:aabb_list) {
for(AxisAlignedBB aabb: aabbs) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
}
return shape;
}
// -------------------------------------------------------------------------------------------------------------------
// JAR resource related
// -------------------------------------------------------------------------------------------------------------------
public static String loadResourceText(InputStream is)
{
try {
if(is==null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
return br.lines().collect(Collectors.joining("\n"));
} catch(Throwable e) {
return "";
}
}
public static String loadResourceText(String path)
{ return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); }
public static void logGitVersion(String mod_name)
{
try {
// Done during construction to have an exact version in case of a crash while registering.
String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim();
logInfo(mod_name+((version.isEmpty())?(" (dev build)"):(" GIT id #"+version)) + ".");
} catch(Throwable e) {
// (void)e; well, then not. Priority is not to get unneeded crashes because of version logging.
}
}
}
/*
* @file Auxiliaries.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General commonly used functionality.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import wile.engineersdecor.ModConfig;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Auxiliaries
{
private static String modid;
private static Logger logger;
private static Supplier<CompoundTag> server_config_supplier = CompoundTag::new;
public static void init(String modid, Logger logger, Supplier<CompoundTag> server_config_supplier)
{
Auxiliaries.modid = modid;
Auxiliaries.logger = logger;
Auxiliaries.server_config_supplier = server_config_supplier;
}
// -------------------------------------------------------------------------------------------------------------------
// Mod specific exports
// -------------------------------------------------------------------------------------------------------------------
public static String modid()
{ return modid; }
public static Logger logger()
{ return logger; }
// -------------------------------------------------------------------------------------------------------------------
// Sideness, system/environment, tagging interfaces
// -------------------------------------------------------------------------------------------------------------------
public interface IExperimentalFeature {}
public static boolean isModLoaded(final String registry_name)
{ return ModList.get().isLoaded(registry_name); }
public static boolean isDevelopmentMode()
{ return SharedConstants.IS_RUNNING_IN_IDE; }
@OnlyIn(Dist.CLIENT)
public static boolean isShiftDown()
{
return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) ||
InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT));
}
@OnlyIn(Dist.CLIENT)
public static boolean isCtrlDown()
{
return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) ||
InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL));
}
// -------------------------------------------------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------------------------------------------------
public static void logInfo(final String msg)
{ logger.info(msg); }
public static void logWarn(final String msg)
{ logger.warn(msg); }
public static void logError(final String msg)
{ logger.error(msg); }
public static void logDebug(final String msg)
{ if(ModConfig.withDebugLogging()) logger.info(msg); }
// -------------------------------------------------------------------------------------------------------------------
// Localization, text formatting
// -------------------------------------------------------------------------------------------------------------------
/**
* Text localisation wrapper, implicitly prepends `MODID` to the
* translation keys. Forces formatting argument, nullable if no special formatting shall be applied..
*/
public static TranslatableComponent localizable(String modtrkey, Object... args)
{
return new TranslatableComponent((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid+"."+modtrkey), args);
}
public static TranslatableComponent localizable(String modtrkey)
{ return localizable(modtrkey, new Object[]{}); }
public static TranslatableComponent localizable_block_key(String blocksubkey)
{ return new TranslatableComponent("block."+modid+"."+blocksubkey); }
@OnlyIn(Dist.CLIENT)
public static String localize(String translationKey, Object... args)
{
TranslatableComponent tr = new TranslatableComponent(translationKey, args);
tr.withStyle(ChatFormatting.RESET);
final String ft = tr.getString();
if(ft.contains("${")) {
// Non-recursive, non-argument lang file entry cross referencing.
Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher mt = pt.matcher(ft);
StringBuffer sb = new StringBuffer();
while(mt.find()) {
String m = mt.group(1);
if(m.contains("?")) {
String[] kv = m.split("\\?", 2);
String key = kv[0].trim();
boolean not = key.startsWith("!");
if(not) key = key.replaceFirst("!", "");
m = kv[1].trim();
if(!server_config_supplier.get().contains(key)) {
m = "";
} else {
boolean r = server_config_supplier.get().getBoolean(key);
if(not) r = !r;
if(!r) m = "";
}
}
mt.appendReplacement(sb, Matcher.quoteReplacement((new TranslatableComponent(m)).getString().trim()));
}
mt.appendTail(sb);
return sb.toString();
} else {
return ft;
}
}
/**
* Returns true if a given key is translated for the current language.
*/
@OnlyIn(Dist.CLIENT)
public static boolean hasTranslation(String key)
{ return net.minecraft.client.resources.language.I18n.exists(key); }
public static final class Tooltip
{
@OnlyIn(Dist.CLIENT)
public static boolean extendedTipCondition()
{ return isShiftDown(); }
@OnlyIn(Dist.CLIENT)
public static boolean helpCondition()
{ return isShiftDown() && isCtrlDown(); }
/**
* Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT.
* Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or
* no translation found).
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List<Component> tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints)
{
// Note: intentionally not using keybinding here, this must be `control` or `shift`.
final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help");
final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip");
if((!help_available) && (!tip_available)) return false;
String tip_text = "";
if(helpCondition()) {
if(help_available) tip_text = localize(helpTranslationKey + ".help");
} else if(extendedTipCondition()) {
if(tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip");
} else if(addAdvancedTooltipHints) {
if(tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : "");
if(help_available) tip_text += localize(modid + ".tooltip.hint.help");
}
if(tip_text.isEmpty()) return false;
String[] tip_list = tip_text.split("\\r?\\n");
for(String tip:tip_list) {
tooltip.add(new TextComponent(tip.replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY));
}
return true;
}
/**
* Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT.
* Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help".
* Return value see method pattern above.
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints)
{ return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); }
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(String translation_key, List<Component> tooltip)
{
if(!Auxiliaries.hasTranslation(translation_key)) return false;
tooltip.add(new TextComponent(localize(translation_key).replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY));
return true;
}
}
@SuppressWarnings("unused")
public static void playerChatMessage(final Player player, final String message)
{
String s = message.trim();
if(!s.isEmpty()) player.sendMessage(new TranslatableComponent(s), new UUID(0,0));
}
public static @Nullable Component unserializeTextComponent(String serialized)
{ return Component.Serializer.fromJson(serialized); }
public static String serializeTextComponent(Component tc)
{ return (tc==null) ? ("") : (Component.Serializer.toJson(tc)); }
// -------------------------------------------------------------------------------------------------------------------
// Item NBT data
// -------------------------------------------------------------------------------------------------------------------
/**
* Equivalent to getDisplayName(), returns null if no custom name is set.
*/
public static @Nullable Component getItemLabel(ItemStack stack)
{
CompoundTag nbt = stack.getTagElement("display");
if(nbt != null && nbt.contains("Name", 8)) {
try {
Component tc = unserializeTextComponent(nbt.getString("Name"));
if(tc != null) return tc;
nbt.remove("Name");
} catch(Exception e) {
nbt.remove("Name");
}
}
return null;
}
public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name)
{
if(name != null) {
CompoundTag nbt = stack.getOrCreateTagElement("display");
nbt.putString("Name", serializeTextComponent(name));
} else {
if(stack.hasTag()) stack.removeTagKey("display");
}
return stack;
}
// -------------------------------------------------------------------------------------------------------------------
// Block handling
// -------------------------------------------------------------------------------------------------------------------
public static boolean isWaterLogged(BlockState state)
{ return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); }
public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1)
{ return new AABB(x0/16.0, y0/16.0, z0/16.0, x1/16.0, y1/16.0, z1/16.0); }
public static AABB getRotatedAABB(AABB bb, Direction new_facing)
{ return getRotatedAABB(bb, new_facing, false); }
public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing)
{ return getRotatedAABB(bb, new_facing, false); }
public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation)
{
if(!horizontal_rotation) {
switch(new_facing.get3DDataValue()) {
case 0: return new AABB(1-bb.maxX, bb.minZ, bb.minY, 1-bb.minX, bb.maxZ, bb.maxY); // D
case 1: return new AABB(1-bb.maxX, 1-bb.maxZ, 1-bb.maxY, 1-bb.minX, 1-bb.minZ, 1-bb.minY); // U
case 2: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AABB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AABB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AABB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
} else {
switch(new_facing.get3DDataValue()) {
case 0: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb
case 1: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb
case 2: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AABB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AABB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AABB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
}
return bb;
}
public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation);
return transformed;
}
public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps)
{
final Direction[] direction_map = new Direction[]{Direction.NORTH,Direction.EAST,Direction.SOUTH,Direction.WEST};
return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps+4096) & 0x03], true);
}
public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps);
return transformed;
}
public static AABB getMirroredAABB(AABB bb, Direction.Axis axis)
{
return switch (axis) {
case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ);
case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ);
case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ);
};
}
public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis);
return transformed;
}
public static VoxelShape getUnionShape(AABB ... aabbs)
{
VoxelShape shape = Shapes.empty();
for(AABB aabb: aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR);
return shape;
}
public static VoxelShape getUnionShape(AABB[] ... aabb_list)
{
VoxelShape shape = Shapes.empty();
for(AABB[] aabbs:aabb_list) {
for(AABB aabb: aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR);
}
return shape;
}
public static final class BlockPosRange implements Iterable<BlockPos>
{
private final int x0, x1, y0, y1, z0, z1;
public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1)
{
this.x0 = Math.min(x0,x1); this.x1 = Math.max(x0,x1);
this.y0 = Math.min(y0,y1); this.y1 = Math.max(y0,y1);
this.z0 = Math.min(z0,z1); this.z1 = Math.max(z0,z1);
}
public static BlockPosRange of(AABB range)
{
return new BlockPosRange(
(int)Math.floor(range.minX),
(int)Math.floor(range.minY),
(int)Math.floor(range.minZ),
(int)Math.floor(range.maxX-.0625),
(int)Math.floor(range.maxY-.0625),
(int)Math.floor(range.maxZ-.0625)
);
}
public int getXSize()
{ return x1-x0+1; }
public int getYSize()
{ return y1-y0+1; }
public int getZSize()
{ return z1-z0+1; }
public int getArea()
{ return getXSize() * getZSize(); }
public int getHeight()
{ return getYSize(); }
public int getVolume()
{ return getXSize() * getYSize() * getZSize(); }
public BlockPos byXZYIndex(int xyz_index)
{
final int xsz=getXSize(), ysz=getYSize(), zsz=getZSize();
xyz_index = xyz_index % (xsz*ysz*zsz);
final int y = xyz_index / (xsz*zsz);
xyz_index -= y * (xsz*zsz);
final int z = xyz_index / xsz;
xyz_index -= z * xsz;
final int x = xyz_index;
return new BlockPos(x0+x, y0+y, z0+z);
}
public BlockPos byXZIndex(int xz_index, int y_offset)
{
final int xsz=getXSize(), zsz=getZSize();
xz_index = xz_index % (xsz*zsz);
final int z = xz_index / xsz;
xz_index -= z * xsz;
final int x = xz_index;
return new BlockPos(x0+x, y0+y_offset, z0+z);
}
public static final class BlockRangeIterator implements Iterator<BlockPos>
{
private final BlockPosRange range_;
private int x,y,z;
public BlockRangeIterator(BlockPosRange range)
{ range_ = range; x=range.x0; y=range.y0; z=range.z0; }
@Override
public boolean hasNext()
{ return (z <= range_.z1); }
@Override
public BlockPos next()
{
if(!hasNext()) throw new NoSuchElementException();
final BlockPos pos = new BlockPos(x,y,z);
++x;
if(x > range_.x1) {
x = range_.x0;
++y;
if(y > range_.y1) {
y = range_.y0;
++z;
}
}
return pos;
}
}
@Override
public BlockRangeIterator iterator()
{ return new BlockRangeIterator(this); }
public Stream<BlockPos> stream()
{ return java.util.stream.StreamSupport.stream(spliterator(), false); }
}
// -------------------------------------------------------------------------------------------------------------------
// JAR resource related
// -------------------------------------------------------------------------------------------------------------------
public static String loadResourceText(InputStream is)
{
try {
if(is==null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
return br.lines().collect(Collectors.joining("\n"));
} catch(Throwable e) {
return "";
}
}
public static String loadResourceText(String path)
{ return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); }
public static void logGitVersion(String mod_name)
{
try {
// Done during construction to have an exact version in case of a crash while registering.
String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim();
logInfo(mod_name+((version.isEmpty())?(" (dev build)"):(" GIT id #"+version)) + ".");
} catch(Throwable e) {
// (void)e; well, then not. Priority is not to get unneeded crashes because of version logging.
}
}
}

View file

@ -0,0 +1,364 @@
/*
* @file Recipes.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe utility functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.NonNullList;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.EnchantmentInstance;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.ComposterBlock;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.brewing.BrewingRecipeRegistry;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.BiPredicate;
public class Crafting
{
// -------------------------------------------------------------------------------------------------------------------
public static final class CraftingGrid extends CraftingContainer
{
protected static final CraftingGrid instance3x3 = new CraftingGrid(3,3);
protected CraftingGrid(int width, int height)
{ super(new AbstractContainerMenu(null,0) { public boolean stillValid(Player player) { return false; } }, width, height); }
protected void fill(Container grid)
{ for(int i=0; i<getContainerSize(); ++i) setItem(i, i>=grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); }
public List<CraftingRecipe> getRecipes(Level world, Container grid)
{ fill(grid); return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); }
public List<ItemStack> getRemainingItems(Level world, Container grid, CraftingRecipe recipe)
{ fill(grid); return recipe.getRemainingItems(this); }
public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe)
{ fill(grid); return recipe.assemble(this); }
}
/**
* Returns a Crafting recipe by registry name.
*/
public static Optional<CraftingRecipe> getCraftingRecipe(Level world, ResourceLocation recipe_id)
{
Recipe<?> recipe = world.getRecipeManager().byKey(recipe_id).orElse(null);
return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe)recipe) : Optional.empty();
}
/**
* Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory.
*/
public static List<CraftingRecipe> get3x3CraftingRecipes(Level world, Container crafting_grid_slots)
{ return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); }
/**
* Returns a recipe by the first N slots (crafting grid slots).
*/
public static Optional<CraftingRecipe> get3x3CraftingRecipe(Level world, Container crafting_grid_slots)
{ return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); }
/**
* Returns the result item of the recipe with the given grid layout.
*/
public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe)
{ return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); }
/**
* Returns the items remaining in the grid after crafting 3x3.
*/
public static List<ItemStack> get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe)
{ return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); }
public static List<ItemStack> get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid)
{
final int width = 3;
final int height = 3;
if(!recipe.canCraftInDimensions(width,height)) return Collections.emptyList();
List<ItemStack> used = new ArrayList<>(); //NonNullList.withSize(width*height);
for(int i=width*height; i>0; --i) used.add(ItemStack.EMPTY);
Container check_inventory = Inventories.copyOf(item_inventory);
Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory);
final List<Ingredient> ingredients = recipe.getIngredients();
final List<ItemStack> preferred = new ArrayList<>(width*height);
if(crafting_grid != null) {
for(int i=0; i<crafting_grid.getContainerSize(); ++i) {
ItemStack stack = crafting_grid.getItem(i);
if(stack.isEmpty()) continue;
stack = stack.copy();
stack.setCount(1);
if(!source.extract(stack).isEmpty()) preferred.add(stack);
}
}
for(int i=0; i<ingredients.size(); ++i) {
final Ingredient ingredient = ingredients.get(i);
if(ingredient == Ingredient.EMPTY) continue;
ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if(!stack.isEmpty()) {
preferred.remove(stack);
} else {
stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if(stack.isEmpty()) return Collections.emptyList();
stack = stack.copy();
stack.setCount(1);
if(source.extract(stack).isEmpty()) return Collections.emptyList();
}
used.set(i, stack);
}
if(recipe instanceof ShapedRecipe shaped) {
List<ItemStack> placement = NonNullList.withSize(width*height, ItemStack.EMPTY);
for(int row=0; row<shaped.getRecipeHeight(); ++row) {
for(int col=0; col<shaped.getRecipeWidth(); ++col) {
placement.set(width*row+col, used.get(row*shaped.getRecipeWidth()+col));
}
}
return placement;
} else {
return used;
}
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns the recipe for a given input stack to smelt, null if there is no recipe
* for the given type (SMELTING,BLASTING,SMOKING, etc).
*/
public static <T extends Recipe<?>> Optional<AbstractCookingRecipe> getFurnaceRecipe(RecipeType<T> recipe_type, Level world, ItemStack input_stack)
{
if(input_stack.isEmpty()) {
return Optional.empty();
} else if(recipe_type == RecipeType.SMELTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else if(recipe_type == RecipeType.BLASTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else if(recipe_type == RecipeType.SMOKING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else {
return Optional.empty();
}
}
public static <T extends Recipe<?>> int getSmeltingTimeNeeded(RecipeType<T> recipe_type, Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0);
return (t<=0) ? 200 : t;
}
/**
* Returns the burn time of an item when used as fuel, 0 if it is no fuel.
*/
public static int getFuelBurntime(Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
int t = ForgeHooks.getBurnTime(stack, null);
return Math.max(t, 0);
}
/**
* Returns true if an item can be used as fuel.
*/
public static boolean isFuel(Level world, ItemStack stack)
{ return (getFuelBurntime(world, stack) > 0) || (stack.getItem()==Items.LAVA_BUCKET); }
/**
* Returns burntime and remaining stack then the item shall be used as fuel.
*/
public static Tuple<Integer,ItemStack> consumeFuel(Level world, ItemStack stack)
{
if(stack.isEmpty()) return new Tuple<>(0,stack);
int burnime = getFuelBurntime(world, stack);
if((stack.getItem()==Items.LAVA_BUCKET)) {
if(burnime <= 0) burnime = 1000*20;
return new Tuple<>(burnime,new ItemStack(Items.BUCKET));
} else if(burnime <= 0) {
return new Tuple<>(0,stack);
} else {
ItemStack left_over = stack.copy();
left_over.shrink(1);
return new Tuple<>(burnime,left_over);
}
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns true if the item can be used as brewing fuel.
*/
public static boolean isBrewingFuel(Level world, ItemStack stack)
{ return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); }
/**
* Returns true if the item can be used as brewing ingredient.
*/
public static boolean isBrewingIngredient(Level world, ItemStack stack)
{ return BrewingRecipeRegistry.isValidIngredient(stack); }
/**
* Returns true if the item can be used as brewing bottle.
*/
public static boolean isBrewingInput(Level world, ItemStack stack)
{ return BrewingRecipeRegistry.isValidInput(stack); }
/**
* Returns the burn time for brewing of the given stack.
*/
public static int getBrewingFuelBurntime(Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
if(stack.getItem() == Items.BLAZE_POWDER) return (400*20);
if(stack.getItem() == Items.BLAZE_ROD) return (400*40);
return 0;
}
/**
* Returns brewing burn time and remaining stack if the item shall be used as fuel.
*/
public static Tuple<Integer,ItemStack> consumeBrewingFuel(Level world, ItemStack stack)
{
int burntime = getBrewingFuelBurntime(world, stack);
if(burntime <= 0) return new Tuple<>(0, stack.copy());
stack = stack.copy();
stack.shrink(1);
return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack);
}
public static final class BrewingOutput
{
public static final int DEFAULT_BREWING_TIME = 400;
public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0,0, DEFAULT_BREWING_TIME);
public final ItemStack item;
public final Container potionInventory;
public final Container ingredientInventory;
public final int potionSlot;
public final int ingredientSlot;
public final int brewTime;
public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed)
{
item = output_potion;
potionInventory = potion_inventory;
ingredientInventory = ingredient_inventory;
potionSlot = potion_slot;
ingredientSlot = ingredient_slot;
brewTime = time_needed;
}
public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory)
{
for(int potion_slot = 0; potion_slot<potion_inventory.getContainerSize(); ++potion_slot) {
final ItemStack pstack = potion_inventory.getItem(potion_slot);
if(!isBrewingInput(world, pstack)) continue;
for(int ingredient_slot = 0; ingredient_slot<ingredient_inventory.getContainerSize(); ++ingredient_slot) {
final ItemStack istack = ingredient_inventory.getItem(ingredient_slot);
if((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) continue;
final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack);
if(result.isEmpty()) continue;
return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME);
}
}
return BrewingOutput.EMPTY;
}
}
// -------------------------------------------------------------------------------------------------------------------
public static double getCompostingChance(ItemStack stack)
{ return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(),0); }
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns the enchtments bound to the given stack.
*/
public static Map<Enchantment, Integer> getEnchantmentsOnItem(Level world, ItemStack stack)
{ return (stack.isEmpty() || (stack.getTag()==null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); }
/**
* Returns an enchanted book with the given enchantment, emtpy stack if not applicable.
*/
public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level)
{ return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); }
/**
* Returns the accumulated repair cost for the given enchantments.
*/
public static int getEnchantmentRepairCost(Level world, Map<Enchantment, Integer> enchantments)
{
int repair_cost = 0;
for(Map.Entry<Enchantment, Integer> e:enchantments.entrySet()) repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost()
return repair_cost;
}
/**
* Trys to add an enchtment to the given stack, returns boolean success.
*/
public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level)
{
if(stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) return false;
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
if(on_item.keySet().stream().anyMatch(ench-> ench.isCompatibleWith(enchantment))) return false;
final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level));
if((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) return false;
final int existing_level = on_item.getOrDefault(enchantment, 0);
if(existing_level > 0) level = Mth.clamp(level+existing_level, 1, enchantment.getMaxLevel());
on_item.put(enchantment, level);
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return true;
}
/**
* Removes enchantments from a stack, returns the removed enchantments.
*/
public static Map<Enchantment, Integer> removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate<Enchantment,Integer> filter)
{
if(stack.isEmpty()) return Collections.emptyMap();
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
final Map<Enchantment, Integer> removed = new HashMap<>();
for(Map.Entry<Enchantment, Integer> e:on_item.entrySet()) {
if(filter.test(e.getKey(), e.getValue())) {
removed.put(e.getKey(), e.getValue());
}
}
for(Enchantment e:removed.keySet()) {
on_item.remove(e);
}
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return removed;
}
}

View file

@ -1,66 +0,0 @@
/*
* @file DataFixing.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Data fixing and mapping correction functionality encapsulation.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent.MissingMappings.Mapping;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class DataFixing
{
private static String modid = "";
private static Map<String, String> item_registry_renaming = new HashMap<>();
private static Map<String, String> block_registry_renaming = new HashMap<>();
public static void init(String mod_id, @Nullable Map<String, String> item_renaming, @Nullable Map<String, String> block_renaming)
{
modid = mod_id;
block_registry_renaming = new HashMap<>();
item_registry_renaming = new HashMap<>();
if(item_renaming!=null) item_registry_renaming.putAll(item_renaming);
if(block_renaming!=null) { block_registry_renaming.putAll(block_renaming); item_registry_renaming.putAll(block_renaming); }
}
public static void onDataFixMissingItemMapping(net.minecraftforge.event.RegistryEvent.MissingMappings<Item> event)
{
// Handler registered in main mod event subscription.
for(Mapping<Item> mapping: event.getMappings()) {
if(mapping.key.getNamespace() != modid) continue;
final String rm = item_registry_renaming.getOrDefault(mapping.key.getPath(), "");
if(rm.isEmpty()) continue;
final Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(modid, rm));
if((item==null) || (item==Items.AIR)) continue;
mapping.remap(item);
}
}
public static void onDataFixMissingBlockMapping(net.minecraftforge.event.RegistryEvent.MissingMappings<Block> event)
{
// Handler registered in main mod event subscription.
for(Mapping<Block> mapping: event.getMappings()) {
if(mapping.key.getNamespace() != modid) continue;
final String rm = block_registry_renaming.getOrDefault(mapping.key.getPath(), "");
if(rm.isEmpty()) continue;
final Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(modid, rm));
if((block==null) || (block==Blocks.AIR)) continue;
mapping.remap(block);
}
}
// @todo: Find a way to register blockstate data fixing.
}

View file

@ -1,385 +1,390 @@
/*
* @file Fluidics.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General fluid handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.Tuple;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidActionResult;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Fluidics
{
/**
* Dedicated fluid handler for a single tank.
*/
public static class SingleTankFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return tank_.isFluidValid(stack); }
@Override public int fill(FluidStack resource, FluidAction action) { return tank_.fill(resource, action); }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
private static class SingleTankOutputFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankOutputFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid().copy(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
/**
* Simple fluid tank, validator concept according to reference implementation by KingLemming.
*/
public static class Tank implements IFluidTank
{
private Predicate<FluidStack> validator_ = ((e)->true);
private BiConsumer<Tank,Integer> interaction_notifier_ = ((tank,diff)->{});
private FluidStack fluid_ = FluidStack.EMPTY;
private int capacity_;
private int fill_rate_;
private int drain_rate_;
public Tank(int capacity)
{ this(capacity, capacity, capacity); }
public Tank(int capacity, int fill_rate, int drain_rate)
{ this(capacity, fill_rate, drain_rate, e->true); }
public Tank(int capacity, int fill_rate, int drain_rate, Predicate<FluidStack> validator)
{
capacity_ = capacity;
setMaxFillRate(fill_rate);
setMaxDrainRate(drain_rate);
setValidator(validator);
}
public Tank load(CompoundNBT nbt)
{
if(nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) {
setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
} else {
clear();
}
return this;
}
public CompoundNBT save(CompoundNBT nbt)
{ if(!isEmpty()) { nbt.put("tank", fluid_.writeToNBT(new CompoundNBT())); } return nbt; }
public void reset()
{ clear(); }
public Tank clear()
{ setFluid(null); return this; }
public int getCapacity()
{ return capacity_; }
public Tank setCapacity(int capacity)
{ capacity_ = capacity; return this; }
public int getMaxDrainRate()
{ return drain_rate_; }
public Tank setMaxDrainRate(int rate)
{ drain_rate_ = MathHelper.clamp(rate, 0, capacity_); return this; }
public int getMaxFillRate()
{ return fill_rate_; }
public Tank setMaxFillRate(int rate)
{ fill_rate_ = MathHelper.clamp(rate, 0, capacity_); return this; }
public Tank setValidator(Predicate<FluidStack> validator)
{ validator_ = (validator!=null) ? validator : ((e)->true); return this; }
public Tank setInteractionNotifier(BiConsumer<Tank,Integer> notifier)
{ interaction_notifier_ = (notifier!=null) ? notifier : ((tank,diff)->{}); return this; }
public LazyOptional<IFluidHandler> createFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); }
public LazyOptional<IFluidHandler> createOutputFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); }
// IFluidTank ------------------------------------------------------------------------------------
@Nonnull
public FluidStack getFluid()
{ return fluid_; }
public void setFluid(@Nullable FluidStack stack)
{ fluid_ = (stack==null) ? FluidStack.EMPTY : stack; }
public int getFluidAmount()
{ return fluid_.getAmount(); }
public boolean isEmpty()
{ return fluid_.isEmpty(); }
public boolean isFull()
{ return getFluidAmount() >= getCapacity(); }
public boolean isFluidValid(FluidStack stack)
{ return validator_.test(stack); }
public boolean isFluidEqual(FluidStack stack)
{ return (stack==null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs==null) || fs.isEmpty() || (!isFluidValid(fs))) {
return 0;
} else if(action.simulate()) {
if(fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount());
if(!fluid_.isFluidEqual(fs)) return 0;
return Math.min(capacity_-fluid_.getAmount(), fs.getAmount());
} else if(fluid_.isEmpty()) {
fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount()));
return fluid_.getAmount();
} else if(!fluid_.isFluidEqual(fs)) {
return 0;
} else {
int amount = capacity_ - fluid_.getAmount();
if(fs.getAmount() < amount) {
fluid_.grow(fs.getAmount());
amount = fs.getAmount();
} else {
fluid_.setAmount(capacity_);
}
if(amount != 0) interaction_notifier_.accept(this, amount);
return amount;
}
}
@Nonnull
public FluidStack drain(int maxDrain)
{ return drain(maxDrain, FluidAction.EXECUTE); }
@Nonnull
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{ return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); }
@Nonnull
@Override
public FluidStack drain(int maxDrain, FluidAction action)
{
final int amount = Math.min(fluid_.getAmount(), maxDrain);
final FluidStack stack = new FluidStack(fluid_, amount);
if((amount > 0) && action.execute()) {
fluid_.shrink(amount);
if(fluid_.isEmpty()) fluid_ = FluidStack.EMPTY;
if(amount != 0) interaction_notifier_.accept(this, -amount);
}
return stack;
}
}
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
*/
public static boolean manualFluidHandlerInteraction(World world, BlockPos pos, @Nullable Direction side, PlayerEntity player, Hand hand)
{ return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
* Returns the fluid and (possibly negative) amount that transferred from the item into the block.
*/
public static @Nullable Tuple<Fluid, Integer> manualTrackedFluidHandlerInteraction(World world, BlockPos pos, @Nullable Direction side, PlayerEntity player, Hand hand)
{
if(world.isClientSide()) return null;
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) return null;
final IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
if(fh==null) return null;
final IItemHandler ih = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).orElse(null);
if(ih==null) return null;
FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) return null;
final ItemStack rstack = far.getResult().copy();
player.setItemInHand(hand, far.getResult());
final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null);
final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null);
if((fh_before==null) || (fh_after==null) || (fh_after.getTanks()!=fh_before.getTanks())) return null; // should not be, but y'never know.
for(int i=0; i<fh_before.getTanks(); ++i) {
final int vol_before = fh_before.getFluidInTank(i).getAmount();
final int vol_after = fh_after.getFluidInTank(i).getAmount();
if(vol_before != vol_after) {
return new Tuple<>(
(vol_before>0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()),
(vol_before - vol_after)
);
}
}
return null;
}
public static int fill(World world, BlockPos pos, Direction side, FluidStack fs, FluidAction action)
{
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
return (fh==null) ? (0) : (fh.fill(fs, action));
}
public static int fill(World world, BlockPos pos, Direction side, FluidStack fs)
{ return fill(world, pos, side, fs, FluidAction.EXECUTE); }
/**
* Fluid tank access when itemized.
*/
public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider
{
private final LazyOptional<IFluidHandlerItem> handler_ = LazyOptional.of(()->this);
private final Function<ItemStack, CompoundNBT> nbt_getter_;
private final BiConsumer<ItemStack, CompoundNBT> nbt_setter_;
private final Predicate<FluidStack> validator_;
private final ItemStack container_;
private final int capacity_;
private final int transfer_rate_;
public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate,
Function<ItemStack, CompoundNBT> nbt_getter,
BiConsumer<ItemStack, CompoundNBT> nbt_setter,
Predicate<FluidStack> validator)
{
container_ = container;
capacity_ = capacity;
transfer_rate_ = transfer_rate;
nbt_getter_ = nbt_getter;
nbt_setter_ = nbt_setter;
validator_ = (validator!=null) ? validator : (e->true);
}
@Override
public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction side)
{ return (capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY) ? handler_.cast() : LazyOptional.empty(); }
protected FluidStack readnbt()
{
final CompoundNBT nbt = nbt_getter_.apply(container_);
return ((nbt==null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt);
}
protected void writenbt(FluidStack fs)
{
CompoundNBT nbt = new CompoundNBT();
if(!fs.isEmpty()) fs.writeToNBT(nbt);
nbt_setter_.accept(container_, nbt);
}
@Override
public ItemStack getContainer()
{ return container_; }
@Override
public int getTanks()
{ return 1; }
@Override
public FluidStack getFluidInTank(int tank)
{ return readnbt(); }
@Override
public int getTankCapacity(int tank)
{ return capacity_; }
@Override
public boolean isFluidValid(int tank, FluidStack fs)
{ return isFluidValid(fs); }
public boolean isFluidValid(FluidStack fs)
{ return validator_.test(fs); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount()!=1))) return 0;
FluidStack tank = readnbt();
final int amount = Math.min(Math.min(fs.getAmount(),transfer_rate_), capacity_-tank.getAmount());
if(amount <= 0) return 0;
if(tank.isEmpty()) {
if(action.execute()) {
tank = new FluidStack(fs.getFluid(), amount, fs.getTag());
writenbt(tank);
}
} else {
if(!tank.isFluidEqual(fs)) {
return 0;
} else if(action.execute()) {
tank.grow(amount);
writenbt(tank);
}
}
return amount;
}
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (container_.getCount()!=1)) return FluidStack.EMPTY;
final FluidStack tank = readnbt();
if((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY;
return drain(fs.getAmount(), action);
}
@Override
public FluidStack drain(int max, FluidAction action)
{
if((max<=0) || (container_.getCount()!=1)) return FluidStack.EMPTY;
FluidStack tank = readnbt();
if(tank.isEmpty()) return FluidStack.EMPTY;
final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_);
final FluidStack fs = tank.copy();
fs.setAmount(amount);
if(action.execute()) { tank.shrink(amount); writenbt(tank); }
return fs;
}
}
}
/*
* @file Fluidics.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General fluid handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.material.Fluid;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidActionResult;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Fluidics
{
public static class SingleTankFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return tank_.isFluidValid(stack); }
@Override public int fill(FluidStack resource, FluidAction action) { return tank_.fill(resource, action); }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
private static class SingleTankOutputFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankOutputFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid().copy(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
public static class Tank implements IFluidTank
{
private Predicate<FluidStack> validator_ = ((e)->true);
private BiConsumer<Tank,Integer> interaction_notifier_ = ((tank,diff)->{});
private FluidStack fluid_ = FluidStack.EMPTY;
private int capacity_;
private int fill_rate_;
private int drain_rate_;
public Tank(int capacity)
{ this(capacity, capacity, capacity); }
public Tank(int capacity, int fill_rate, int drain_rate)
{ this(capacity, fill_rate, drain_rate, e->true); }
public Tank(int capacity, int fill_rate, int drain_rate, Predicate<FluidStack> validator)
{
capacity_ = capacity;
setMaxFillRate(fill_rate);
setMaxDrainRate(drain_rate);
setValidator(validator);
}
public Tank load(CompoundTag nbt)
{
if(nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) {
setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
} else {
clear();
}
return this;
}
public CompoundTag save(CompoundTag nbt)
{ if(!isEmpty()) { nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); } return nbt; }
public void reset()
{ clear(); }
public Tank clear()
{ setFluid(null); return this; }
public int getCapacity()
{ return capacity_; }
public Tank setCapacity(int capacity)
{ capacity_ = capacity; return this; }
public int getMaxDrainRate()
{ return drain_rate_; }
public Tank setMaxDrainRate(int rate)
{ drain_rate_ = Mth.clamp(rate, 0, capacity_); return this; }
public int getMaxFillRate()
{ return fill_rate_; }
public Tank setMaxFillRate(int rate)
{ fill_rate_ = Mth.clamp(rate, 0, capacity_); return this; }
public Tank setValidator(Predicate<FluidStack> validator)
{ validator_ = (validator!=null) ? validator : ((e)->true); return this; }
public Tank setInteractionNotifier(BiConsumer<Tank,Integer> notifier)
{ interaction_notifier_ = (notifier!=null) ? notifier : ((tank,diff)->{}); return this; }
public LazyOptional<IFluidHandler> createFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); }
public LazyOptional<IFluidHandler> createOutputFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); }
// IFluidTank ------------------------------------------------------------------------------------
@Nonnull
public FluidStack getFluid()
{ return fluid_; }
public void setFluid(@Nullable FluidStack stack)
{ fluid_ = (stack==null) ? FluidStack.EMPTY : stack; }
public int getFluidAmount()
{ return fluid_.getAmount(); }
public boolean isEmpty()
{ return fluid_.isEmpty(); }
public boolean isFull()
{ return getFluidAmount() >= getCapacity(); }
public boolean isFluidValid(FluidStack stack)
{ return validator_.test(stack); }
public boolean isFluidEqual(FluidStack stack)
{ return (stack==null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs==null) || fs.isEmpty() || (!isFluidValid(fs))) {
return 0;
} else if(action.simulate()) {
if(fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount());
if(!fluid_.isFluidEqual(fs)) return 0;
return Math.min(capacity_-fluid_.getAmount(), fs.getAmount());
} else if(fluid_.isEmpty()) {
fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount()));
return fluid_.getAmount();
} else if(!fluid_.isFluidEqual(fs)) {
return 0;
} else {
int amount = capacity_ - fluid_.getAmount();
if(fs.getAmount() < amount) {
fluid_.grow(fs.getAmount());
amount = fs.getAmount();
} else {
fluid_.setAmount(capacity_);
}
if(amount != 0) interaction_notifier_.accept(this, amount);
return amount;
}
}
@Nonnull
public FluidStack drain(int maxDrain)
{ return drain(maxDrain, FluidAction.EXECUTE); }
@Nonnull
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{ return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); }
@Nonnull
@Override
public FluidStack drain(int maxDrain, FluidAction action)
{
final int amount = Math.min(fluid_.getAmount(), maxDrain);
final FluidStack stack = new FluidStack(fluid_, amount);
if((amount > 0) && action.execute()) {
fluid_.shrink(amount);
if(fluid_.isEmpty()) fluid_ = FluidStack.EMPTY;
if(amount != 0) interaction_notifier_.accept(this, -amount);
}
return stack;
}
}
// -------------------------------------------------------------------------------------------------------------------
public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side)
{ return FluidUtil.getFluidHandler(world, pos, side).orElse(null); }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
*/
public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand)
{ return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; }
public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler)
{ return FluidUtil.interactWithFluidHandler(player, hand, handler); }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
* Returns the fluid and (possibly negative) amount that transferred from the item into the block.
*/
public static @Nullable Tuple<Fluid, Integer> manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand)
{
if(world.isClientSide()) return null;
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) return null;
final IFluidHandler fh = handler(world, pos, side);
if(fh==null) return null;
final IItemHandler ih = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).orElse(null);
if(ih==null) return null;
FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) return null;
final ItemStack rstack = far.getResult().copy();
player.setItemInHand(hand, far.getResult());
final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null);
final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null);
if((fh_before==null) || (fh_after==null) || (fh_after.getTanks()!=fh_before.getTanks())) return null; // should not be, but y'never know.
for(int i=0; i<fh_before.getTanks(); ++i) {
final int vol_before = fh_before.getFluidInTank(i).getAmount();
final int vol_after = fh_after.getFluidInTank(i).getAmount();
if(vol_before != vol_after) {
return new Tuple<>(
(vol_before>0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()),
(vol_before - vol_after)
);
}
}
return null;
}
public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side)
{ return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); }
public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action)
{
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
return (fh==null) ? (0) : (fh.fill(fs, action));
}
public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs)
{ return fill(world, pos, side, fs, FluidAction.EXECUTE); }
/**
* Fluid tank access when itemized.
*/
public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider
{
private final LazyOptional<IFluidHandlerItem> handler_ = LazyOptional.of(()->this);
private final Function<ItemStack, CompoundTag> nbt_getter_;
private final BiConsumer<ItemStack, CompoundTag> nbt_setter_;
private final Predicate<FluidStack> validator_;
private final ItemStack container_;
private final int capacity_;
private final int transfer_rate_;
public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate,
Function<ItemStack, CompoundTag> nbt_getter,
BiConsumer<ItemStack, CompoundTag> nbt_setter,
Predicate<FluidStack> validator)
{
container_ = container;
capacity_ = capacity;
transfer_rate_ = transfer_rate;
nbt_getter_ = nbt_getter;
nbt_setter_ = nbt_setter;
validator_ = (validator!=null) ? validator : (e->true);
}
@Override
public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction side)
{ return (capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY) ? handler_.cast() : LazyOptional.empty(); }
protected FluidStack readnbt()
{
final CompoundTag nbt = nbt_getter_.apply(container_);
return ((nbt==null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt);
}
protected void writenbt(FluidStack fs)
{
CompoundTag nbt = new CompoundTag();
if(!fs.isEmpty()) fs.writeToNBT(nbt);
nbt_setter_.accept(container_, nbt);
}
@Override
public ItemStack getContainer()
{ return container_; }
@Override
public int getTanks()
{ return 1; }
@Override
public FluidStack getFluidInTank(int tank)
{ return readnbt(); }
@Override
public int getTankCapacity(int tank)
{ return capacity_; }
@Override
public boolean isFluidValid(int tank, FluidStack fs)
{ return isFluidValid(fs); }
public boolean isFluidValid(FluidStack fs)
{ return validator_.test(fs); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount()!=1))) return 0;
FluidStack tank = readnbt();
final int amount = Math.min(Math.min(fs.getAmount(),transfer_rate_), capacity_-tank.getAmount());
if(amount <= 0) return 0;
if(tank.isEmpty()) {
if(action.execute()) {
tank = new FluidStack(fs.getFluid(), amount, fs.getTag());
writenbt(tank);
}
} else {
if(!tank.isFluidEqual(fs)) {
return 0;
} else if(action.execute()) {
tank.grow(amount);
writenbt(tank);
}
}
return amount;
}
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (container_.getCount()!=1)) return FluidStack.EMPTY;
final FluidStack tank = readnbt();
if((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY;
return drain(fs.getAmount(), action);
}
@Override
public FluidStack drain(int max, FluidAction action)
{
if((max<=0) || (container_.getCount()!=1)) return FluidStack.EMPTY;
FluidStack tank = readnbt();
if(tank.isEmpty()) return FluidStack.EMPTY;
final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_);
final FluidStack fs = tank.copy();
fs.setAmount(amount);
if(action.execute()) { tank.shrink(amount); writenbt(tank); }
return fs;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,309 +1,309 @@
/*
* @file Networking.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Main client/server message handling.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.world.World;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.inventory.container.Container;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkRegistry;
import net.minecraftforge.fml.network.simple.SimpleChannel;
import net.minecraftforge.fml.network.NetworkDirection;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Networking
{
private static final String PROTOCOL = "1";
private static SimpleChannel DEFAULT_CHANNEL;
public static void init(String modid)
{
DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder
.named(new ResourceLocation(modid, "default_ch"))
.clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL)
.simpleChannel();
int discr = -1;
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle);
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity notifications
//--------------------------------------------------------------------------------------------------------------------
public interface IPacketTileNotifyReceiver
{
default void onServerPacketReceived(CompoundNBT nbt) {}
default void onClientPacketReceived(PlayerEntity player, CompoundNBT nbt) {}
}
public static class PacketTileNotifyClientToServer
{
CompoundNBT nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToServer(BlockPos pos, CompoundNBT nbt)
{ if((pos!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); }
public static void sendToServer(TileEntity te, CompoundNBT nbt)
{ if((te!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); }
public PacketTileNotifyClientToServer()
{}
public PacketTileNotifyClientToServer(BlockPos pos, CompoundNBT nbt)
{ this.nbt = nbt; this.pos = pos; }
public PacketTileNotifyClientToServer(TileEntity te, CompoundNBT nbt)
{ this.nbt = nbt; pos = te.getBlockPos(); }
public static PacketTileNotifyClientToServer parse(final PacketBuffer buf)
{ return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyClientToServer pkt, final PacketBuffer buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = ctx.get().getSender();
World world = player.level;
if(world == null) return;
final TileEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketTileNotifyServerToClient
{
CompoundNBT nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToPlayer(PlayerEntity player, TileEntity te, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (te==null) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayers(TileEntity te, CompoundNBT nbt)
{
if(te==null || te.getLevel().isClientSide()) return;
for(PlayerEntity player: te.getLevel().players()) sendToPlayer(player, te, nbt);
}
public PacketTileNotifyServerToClient()
{}
public PacketTileNotifyServerToClient(BlockPos pos, CompoundNBT nbt)
{ this.nbt=nbt; this.pos=pos; }
public PacketTileNotifyServerToClient(TileEntity te, CompoundNBT nbt)
{ this.nbt=nbt; pos=te.getBlockPos(); }
public static PacketTileNotifyServerToClient parse(final PacketBuffer buf)
{ return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyServerToClient pkt, final PacketBuffer buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
World world = SidedProxy.getWorldClientSide();
if(world == null) return;
final TileEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onServerPacketReceived(pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// (GUI) Container synchrsonisation
//--------------------------------------------------------------------------------------------------------------------
public interface INetworkSynchronisableContainer
{
void onServerPacketReceived(int windowId, CompoundNBT nbt);
void onClientPacketReceived(int windowId, PlayerEntity player, CompoundNBT nbt);
}
public static class PacketContainerSyncClientToServer
{
int id = -1;
CompoundNBT nbt = null;
public static void sendToServer(int windowId, CompoundNBT nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); }
public static void sendToServer(Container container, CompoundNBT nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); }
public PacketContainerSyncClientToServer()
{}
public PacketContainerSyncClientToServer(int id, CompoundNBT nbt)
{ this.nbt = nbt; this.id = id; }
public static PacketContainerSyncClientToServer parse(final PacketBuffer buf)
{ return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncClientToServer pkt, final PacketBuffer buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = ctx.get().getSender();
if(!(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onClientPacketReceived(pkt.id, player,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketContainerSyncServerToClient
{
int id = -1;
CompoundNBT nbt = null;
public static void sendToPlayer(PlayerEntity player, int windowId, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayer(PlayerEntity player, Container container, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static <C extends Container & INetworkSynchronisableContainer>
void sendToListeners(World world, C container, CompoundNBT nbt)
{
for(PlayerEntity player: world.players()) {
if(player.containerMenu.containerId != container.containerId) continue;
sendToPlayer(player, container.containerId, nbt);
}
}
public PacketContainerSyncServerToClient()
{}
public PacketContainerSyncServerToClient(int id, CompoundNBT nbt)
{ this.nbt=nbt; this.id=id; }
public static PacketContainerSyncServerToClient parse(final PacketBuffer buf)
{ return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncServerToClient pkt, final PacketBuffer buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = SidedProxy.getPlayerClientSide();
if(!(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onServerPacketReceived(pkt.id,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Main window GUI text message
//--------------------------------------------------------------------------------------------------------------------
public static class OverlayTextMessage
{
public static final int DISPLAY_TIME_MS = 3000;
private static BiConsumer<ITextComponent, Integer> handler_ = null;
private ITextComponent data_;
private int delay_ = DISPLAY_TIME_MS;
private ITextComponent data() { return data_; }
private int delay() { return delay_; }
public static void setHandler(BiConsumer<ITextComponent, Integer> handler)
{ if(handler_==null) handler_ = handler; }
public static void sendToPlayer(PlayerEntity player, ITextComponent message, int delay)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer)) return;
DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public OverlayTextMessage()
{ data_ = new TranslationTextComponent("[unset]"); }
public OverlayTextMessage(final ITextComponent tct, int delay)
{ data_ = (ITextComponent)tct.copy(); delay_ = delay; }
public static OverlayTextMessage parse(final PacketBuffer buf)
{
try {
return new OverlayTextMessage((ITextComponent)buf.readComponent(), DISPLAY_TIME_MS);
} catch(Throwable e) {
return new OverlayTextMessage(new TranslationTextComponent("[incorrect translation]"), DISPLAY_TIME_MS);
}
}
public static void compose(final OverlayTextMessage pkt, final PacketBuffer buf)
{
try {
buf.writeComponent(pkt.data());
} catch(Throwable e) {
Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e.toString());
}
}
public static class Handler
{
public static void handle(final OverlayTextMessage pkt, final Supplier<NetworkEvent.Context> ctx)
{
if(handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay()));
ctx.get().setPacketHandled(true);
}
}
}
}
/*
* @file Networking.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Main client/server message handling.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fmllegacy.network.NetworkEvent;
import net.minecraftforge.fmllegacy.network.NetworkDirection;
import net.minecraftforge.fmllegacy.network.NetworkRegistry;
import net.minecraftforge.fmllegacy.network.simple.SimpleChannel;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Networking
{
private static final String PROTOCOL = "1";
private static SimpleChannel DEFAULT_CHANNEL;
public static void init(String modid)
{
DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder
.named(new ResourceLocation(modid, "default_ch"))
.clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL)
.simpleChannel();
int discr = -1;
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle);
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity notifications
//--------------------------------------------------------------------------------------------------------------------
public interface IPacketTileNotifyReceiver
{
default void onServerPacketReceived(CompoundTag nbt) {}
default void onClientPacketReceived(Player player, CompoundTag nbt) {}
}
public static class PacketTileNotifyClientToServer
{
CompoundTag nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToServer(BlockPos pos, CompoundTag nbt)
{ if((pos!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); }
public static void sendToServer(BlockEntity te, CompoundTag nbt)
{ if((te!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); }
public PacketTileNotifyClientToServer()
{}
public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt)
{ this.nbt = nbt; this.pos = pos; }
public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt)
{ this.nbt = nbt; pos = te.getBlockPos(); }
public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf)
{ return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = ctx.get().getSender();
if(player==null) return;
Level world = player.level;
final BlockEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketTileNotifyServerToClient
{
CompoundTag nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te==null) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayers(BlockEntity te, CompoundTag nbt)
{
if(te==null || te.getLevel()==null) return;
for(Player player: te.getLevel().players()) sendToPlayer(player, te, nbt);
}
public PacketTileNotifyServerToClient()
{}
public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt)
{ this.nbt=nbt; this.pos=pos; }
public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt)
{ this.nbt=nbt; pos=te.getBlockPos(); }
public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf)
{ return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Level world = SidedProxy.getWorldClientSide();
if(world == null) return;
final BlockEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onServerPacketReceived(pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// (GUI) Container synchronization
//--------------------------------------------------------------------------------------------------------------------
public interface INetworkSynchronisableContainer
{
void onServerPacketReceived(int windowId, CompoundTag nbt);
void onClientPacketReceived(int windowId, Player player, CompoundTag nbt);
}
public static class PacketContainerSyncClientToServer
{
int id = -1;
CompoundTag nbt = null;
public static void sendToServer(int windowId, CompoundTag nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); }
public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); }
public PacketContainerSyncClientToServer()
{}
public PacketContainerSyncClientToServer(int id, CompoundTag nbt)
{ this.nbt = nbt; this.id = id; }
public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf)
{ return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = ctx.get().getSender();
if((player==null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onClientPacketReceived(pkt.id, player,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketContainerSyncServerToClient
{
int id = -1;
CompoundTag nbt = null;
public static void sendToPlayer(Player player, int windowId, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static <C extends AbstractContainerMenu & INetworkSynchronisableContainer>
void sendToListeners(Level world, C container, CompoundTag nbt)
{
for(Player player: world.players()) {
if(player.containerMenu.containerId != container.containerId) continue;
sendToPlayer(player, container.containerId, nbt);
}
}
public PacketContainerSyncServerToClient()
{}
public PacketContainerSyncServerToClient(int id, CompoundTag nbt)
{ this.nbt=nbt; this.id=id; }
public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf)
{ return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = SidedProxy.getPlayerClientSide();
if((player==null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onServerPacketReceived(pkt.id,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Main window GUI text message
//--------------------------------------------------------------------------------------------------------------------
public static class OverlayTextMessage
{
public static final int DISPLAY_TIME_MS = 3000;
private static BiConsumer<Component, Integer> handler_ = null;
private final Component data_;
private int delay_ = DISPLAY_TIME_MS;
private Component data() { return data_; }
private int delay() { return delay_; }
public static void setHandler(BiConsumer<Component, Integer> handler)
{ if(handler_==null) handler_ = handler; }
public static void sendToPlayer(Player player, Component message, int delay)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer)) return;
DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public OverlayTextMessage()
{ data_ = new TranslatableComponent("[unset]"); }
public OverlayTextMessage(final Component tct, int delay)
{ data_ = tct.copy(); delay_ = delay; }
public static OverlayTextMessage parse(final FriendlyByteBuf buf)
{
try {
return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS);
} catch(Throwable e) {
return new OverlayTextMessage(new TranslatableComponent("[incorrect translation]"), DISPLAY_TIME_MS);
}
}
public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf)
{
try {
buf.writeComponent(pkt.data());
} catch(Throwable e) {
Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e);
}
}
public static class Handler
{
public static void handle(final OverlayTextMessage pkt, final Supplier<NetworkEvent.Context> ctx)
{
if(handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay()));
ctx.get().setPacketHandled(true);
}
}
}
}

View file

@ -1,199 +1,197 @@
/*
* @file OptionalRecipeCondition.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe condition to enable opt'ing out JSON based recipes.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.tags.ITag;
import net.minecraft.tags.TagCollectionManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.JSONUtils;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.crafting.conditions.IConditionSerializer;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.ForgeRegistries;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class OptionalRecipeCondition implements ICondition
{
private static ResourceLocation NAME;
private final List<ResourceLocation> all_required;
private final List<ResourceLocation> any_missing;
private final List<ResourceLocation> all_required_tags;
private final List<ResourceLocation> any_missing_tags;
private final @Nullable ResourceLocation result;
private final boolean result_is_tag;
private final boolean experimental;
private static boolean with_experimental = false;
private static boolean without_recipes = false;
private static Predicate<Block> block_optouts = (block)->false;
private static Predicate<Item> item_optouts = (item)->false;
public static void init(String modid, Logger logger)
{
NAME = new ResourceLocation(modid, "optional");
}
public static void on_config(boolean enable_experimental, boolean disable_all_recipes,
Predicate<Block> block_optout_provider,
Predicate<Item> item_optout_provider)
{
with_experimental = enable_experimental;
without_recipes = disable_all_recipes;
block_optouts = block_optout_provider;
item_optouts = item_optout_provider;
}
public OptionalRecipeCondition(ResourceLocation result, List<ResourceLocation> required, List<ResourceLocation> missing, List<ResourceLocation> required_tags, List<ResourceLocation> missing_tags, boolean isexperimental, boolean result_is_tag)
{
all_required = required;
any_missing = missing;
all_required_tags = required_tags;
any_missing_tags = missing_tags;
this.result = result;
this.result_is_tag = result_is_tag;
experimental=isexperimental;
}
@Override
public ResourceLocation getID()
{ return NAME; }
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Optional recipe, all-required: [");
for(ResourceLocation e:all_required) sb.append(e.toString()).append(",");
for(ResourceLocation e:all_required_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("], any-missing: [");
for(ResourceLocation e:any_missing) sb.append(e.toString()).append(",");
for(ResourceLocation e:any_missing_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("]");
if(experimental) sb.append(" EXPERIMENTAL");
return sb.toString();
}
@Override
public boolean test()
{
if(without_recipes) return false;
if((experimental) && (!with_experimental)) return false;
final IForgeRegistry<Item> item_registry = ForgeRegistries.ITEMS;
final Map<ResourceLocation, ITag<Item>> item_tags = TagCollectionManager.getInstance().getItems().getAllTags();
if(result != null) {
boolean item_registered = item_registry.containsKey(result);
if(!item_registered) return false; // required result not registered
if(item_registered && item_optouts.test(item_registry.getValue(result))) return false;
if(ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) return false;
}
if(!all_required.isEmpty()) {
for(ResourceLocation rl:all_required) {
if(!item_registry.containsKey(rl)) return false;
}
}
if(!all_required_tags.isEmpty()) {
for(ResourceLocation rl:all_required_tags) {
if(!item_tags.containsKey(rl)) return false;
if(item_tags.get(rl).getValues().isEmpty()) return false;
}
}
if(!any_missing.isEmpty()) {
for(ResourceLocation rl:any_missing) {
if(!item_registry.containsKey(rl)) return true;
}
return false;
}
if(!any_missing_tags.isEmpty()) {
for(ResourceLocation rl:any_missing_tags) {
if(!item_tags.containsKey(rl)) return true;
if(item_tags.get(rl).getValues().isEmpty()) return true;
}
return false;
}
return true;
}
public static class Serializer implements IConditionSerializer<OptionalRecipeCondition>
{
public static final Serializer INSTANCE = new Serializer();
@Override
public ResourceLocation getID()
{ return OptionalRecipeCondition.NAME; }
@Override
public void write(JsonObject json, OptionalRecipeCondition condition)
{
JsonArray required = new JsonArray();
JsonArray missing = new JsonArray();
for(ResourceLocation e:condition.all_required) required.add(e.toString());
for(ResourceLocation e:condition.any_missing) missing.add(e.toString());
json.add("required", required);
json.add("missing", missing);
if(condition.result != null) {
json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result.toString());
}
}
@Override
public OptionalRecipeCondition read(JsonObject json)
{
List<ResourceLocation> required = new ArrayList<>();
List<ResourceLocation> missing = new ArrayList<>();
List<ResourceLocation> required_tags = new ArrayList<>();
List<ResourceLocation> missing_tags = new ArrayList<>();
ResourceLocation result = null;
boolean experimental = false;
boolean result_is_tag = false;
if(json.has("result")) {
String s = json.get("result").getAsString();
if(s.startsWith("#")) {
result = new ResourceLocation(s.substring(1));
result_is_tag = true;
} else {
result = new ResourceLocation(s);
}
}
if(json.has("required")) {
for(JsonElement e:JSONUtils.getAsJsonArray(json, "required")) {
String s = e.getAsString();
if(s.startsWith("#")) {
required_tags.add(new ResourceLocation(s.substring(1)));
} else {
required.add(new ResourceLocation(s));
}
}
}
if(json.has("missing")) {
for(JsonElement e:JSONUtils.getAsJsonArray(json, "missing")) {
String s = e.getAsString();
if(s.startsWith("#")) {
missing_tags.add(new ResourceLocation(s.substring(1)));
} else {
missing.add(new ResourceLocation(s));
}
}
}
if(json.has("experimental")) experimental = json.get("experimental").getAsBoolean();
return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag);
}
}
}
/*
* @file OptionalRecipeCondition.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe condition to enable opt'ing out JSON based recipes.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.*;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.crafting.conditions.IConditionSerializer;
import net.minecraftforge.registries.ForgeRegistries;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraftforge.registries.IForgeRegistry;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class OptionalRecipeCondition implements ICondition
{
private static ResourceLocation NAME;
private final List<ResourceLocation> all_required;
private final List<ResourceLocation> any_missing;
private final List<ResourceLocation> all_required_tags;
private final List<ResourceLocation> any_missing_tags;
private final @Nullable ResourceLocation result;
private final boolean result_is_tag;
private final boolean experimental;
private static boolean with_experimental = false;
private static boolean without_recipes = false;
private static Predicate<Block> block_optouts = (block)->false;
private static Predicate<Item> item_optouts = (item)->false;
public static void init(String modid, Logger logger)
{
NAME = new ResourceLocation(modid, "optional");
}
public static void on_config(boolean enable_experimental, boolean disable_all_recipes,
Predicate<Block> block_optout_provider,
Predicate<Item> item_optout_provider)
{
with_experimental = enable_experimental;
without_recipes = disable_all_recipes;
block_optouts = block_optout_provider;
item_optouts = item_optout_provider;
}
public OptionalRecipeCondition(ResourceLocation result, List<ResourceLocation> required, List<ResourceLocation> missing, List<ResourceLocation> required_tags, List<ResourceLocation> missing_tags, boolean isexperimental, boolean result_is_tag)
{
all_required = required;
any_missing = missing;
all_required_tags = required_tags;
any_missing_tags = missing_tags;
this.result = result;
this.result_is_tag = result_is_tag;
experimental=isexperimental;
}
@Override
public ResourceLocation getID()
{ return NAME; }
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Optional recipe, all-required: [");
for(ResourceLocation e:all_required) sb.append(e.toString()).append(",");
for(ResourceLocation e:all_required_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("], any-missing: [");
for(ResourceLocation e:any_missing) sb.append(e.toString()).append(",");
for(ResourceLocation e:any_missing_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("]");
if(experimental) sb.append(" EXPERIMENTAL");
return sb.toString();
}
@Override
public boolean test()
{
if(without_recipes) return false;
if((experimental) && (!with_experimental)) return false;
final IForgeRegistry<Item> item_registry = ForgeRegistries.ITEMS;
final Collection<ResourceLocation> item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags();
if(result != null) {
boolean item_registered = item_registry.containsKey(result);
if(!item_registered) return false; // required result not registered
if(item_registered && item_optouts.test(item_registry.getValue(result))) return false;
if(ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) return false;
}
if(!all_required.isEmpty()) {
for(ResourceLocation rl:all_required) {
if(!item_registry.containsKey(rl)) return false;
}
}
if(!all_required_tags.isEmpty()) {
for(ResourceLocation rl:all_required_tags) {
if(!item_tags.contains(rl)) return false;
}
}
if(!any_missing.isEmpty()) {
for(ResourceLocation rl:any_missing) {
if(!item_registry.containsKey(rl)) return true;
}
return false;
}
if(!any_missing_tags.isEmpty()) {
for(ResourceLocation rl:any_missing_tags) {
if(!item_tags.contains(rl)) return true;
}
return false;
}
return true;
}
public static class Serializer implements IConditionSerializer<OptionalRecipeCondition>
{
public static final Serializer INSTANCE = new Serializer();
@Override
public ResourceLocation getID()
{ return OptionalRecipeCondition.NAME; }
@Override
public void write(JsonObject json, OptionalRecipeCondition condition)
{
JsonArray required = new JsonArray();
JsonArray missing = new JsonArray();
for(ResourceLocation e:condition.all_required) required.add(e.toString());
for(ResourceLocation e:condition.any_missing) missing.add(e.toString());
json.add("required", required);
json.add("missing", missing);
if(condition.result != null) {
json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result);
}
}
@Override
public OptionalRecipeCondition read(JsonObject json)
{
List<ResourceLocation> required = new ArrayList<>();
List<ResourceLocation> missing = new ArrayList<>();
List<ResourceLocation> required_tags = new ArrayList<>();
List<ResourceLocation> missing_tags = new ArrayList<>();
ResourceLocation result = null;
boolean experimental = false;
boolean result_is_tag = false;
if(json.has("result")) {
String s = json.get("result").getAsString();
if(s.startsWith("#")) {
result = new ResourceLocation(s.substring(1));
result_is_tag = true;
} else {
result = new ResourceLocation(s);
}
}
if(json.has("required")) {
for(JsonElement e:GsonHelper.getAsJsonArray(json, "required")) {
String s = e.getAsString();
if(s.startsWith("#")) {
required_tags.add(new ResourceLocation(s.substring(1)));
} else {
required.add(new ResourceLocation(s));
}
}
}
if(json.has("missing")) {
for(JsonElement e:GsonHelper.getAsJsonArray(json, "missing")) {
String s = e.getAsString();
if(s.startsWith("#")) {
missing_tags.add(new ResourceLocation(s.substring(1)));
} else {
missing.add(new ResourceLocation(s));
}
}
}
if(json.has("experimental")) experimental = json.get("experimental").getAsBoolean();
return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag);
}
}
}

View file

@ -1,117 +1,116 @@
/*
* @file Overlay.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Renders status messages in one line.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.MainWindow;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.AbstractGui;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
public class Overlay
{
public static void register()
{
if(SidedProxy.mc() != null) {
MinecraftForge.EVENT_BUS.register(new TextOverlayGui());
Networking.OverlayTextMessage.setHandler(TextOverlayGui::show);
}
}
public static void show(PlayerEntity player, final ITextComponent message)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); }
public static void show(PlayerEntity player, final ITextComponent message, int delay)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, delay); }
// -----------------------------------------------------------------------------
// Client side handler
// -----------------------------------------------------------------------------
@Mod.EventBusSubscriber(Dist.CLIENT)
@OnlyIn(Dist.CLIENT)
public static class TextOverlayGui extends AbstractGui
{
private static final ITextComponent EMPTY_TEXT = new StringTextComponent("");
private static double overlay_y_ = 0.75;
private static int text_color_ = 0x00ffaa00;
private static int border_color_ = 0xaa333333;
private static int background_color1_ = 0xaa333333;
private static int background_color2_ = 0xaa444444;
private final Minecraft mc;
private static long deadline_;
private static ITextComponent text_;
public static void on_config(double overlay_y)
{
overlay_y_ = overlay_y;
// currently const, just to circumvent "useless variable" warnings
text_color_ = 0x00ffaa00;
border_color_ = 0xaa333333;
background_color1_ = 0xaa333333;
background_color2_ = 0xaa444444;
}
public static synchronized ITextComponent text()
{ return text_; }
public static synchronized long deadline()
{ return deadline_; }
public static synchronized void hide()
{ deadline_ = 0; text_ = EMPTY_TEXT; }
public static synchronized void show(ITextComponent s, int displayTimeoutMs)
{ text_ = (s==null)?(EMPTY_TEXT):(s.copy()); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
public static synchronized void show(String s, int displayTimeoutMs)
{ text_ = ((s==null)||(s.isEmpty()))?(EMPTY_TEXT):(new StringTextComponent(s)); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
TextOverlayGui()
{ super(); mc = SidedProxy.mc(); }
@SubscribeEvent
public void onRenderGui(RenderGameOverlayEvent.Post event)
{
if(event.getType() != RenderGameOverlayEvent.ElementType.CHAT) return;
if(deadline() < System.currentTimeMillis()) return;
if(text()==EMPTY_TEXT) return;
String txt = text().getString();
if(txt.isEmpty()) return;
MatrixStack mxs = event.getMatrixStack();
final MainWindow win = mc.getWindow();
final FontRenderer fr = mc.font;
final boolean was_unicode = fr.isBidirectional();
try {
final int cx = win.getGuiScaledWidth() / 2;
final int cy = (int)(win.getGuiScaledHeight() * overlay_y_);
final int w = fr.width(txt);
final int h = fr.lineHeight;
fillGradient(mxs, cx-(w/2)-3, cy-2, cx+(w/2)+2, cy+h+2, 0xaa333333, 0xaa444444);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy-2, 0xaa333333);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy+h+2, 0xaa333333);
vLine(mxs, cx-(w/2)-3, cy-2, cy+h+2, 0xaa333333);
vLine(mxs, cx+(w/2)+2, cy-2, cy+h+2, 0xaa333333);
drawCenteredString(mxs, fr, text(), cx , cy+1, 0x00ffaa00);
} finally {
; // fr.setBidiFlag(was_unicode);
}
}
}
}
/*
* @file Overlay.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Renders status messages in one line.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.Font;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
public class Overlay
{
public static void register()
{
if(SidedProxy.mc() != null) {
MinecraftForge.EVENT_BUS.register(new TextOverlayGui());
Networking.OverlayTextMessage.setHandler(TextOverlayGui::show);
}
}
public static void show(Player player, final Component message)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); }
public static void show(Player player, final Component message, int delay)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, delay); }
// -----------------------------------------------------------------------------
// Client side handler
// -----------------------------------------------------------------------------
@Mod.EventBusSubscriber(Dist.CLIENT)
@OnlyIn(Dist.CLIENT)
public static class TextOverlayGui extends Screen
{
private static final Component EMPTY_TEXT = new TextComponent("");
private static double overlay_y_ = 0.75;
private static int text_color_ = 0x00ffaa00;
private static int border_color_ = 0xaa333333;
private static int background_color1_ = 0xaa333333;
private static int background_color2_ = 0xaa444444;
private final Minecraft mc;
private static long deadline_;
private static Component text_;
public static void on_config(double overlay_y)
{ on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); }
public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2)
{
overlay_y_ = overlay_y;
text_color_ = text_color;
border_color_ = border_color;
background_color1_ = background_color1;
background_color2_ = background_color2;
}
public static synchronized Component text()
{ return text_; }
public static synchronized long deadline()
{ return deadline_; }
public static synchronized void hide()
{ deadline_ = 0; text_ = EMPTY_TEXT; }
public static synchronized void show(Component s, int displayTimeoutMs)
{ text_ = (s==null)?(EMPTY_TEXT):(s.copy()); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
public static synchronized void show(String s, int displayTimeoutMs)
{ text_ = ((s==null)||(s.isEmpty()))?(EMPTY_TEXT):(new TextComponent(s)); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
TextOverlayGui()
{ super(new TextComponent("")); mc = SidedProxy.mc(); }
@SubscribeEvent
public void onRenderGui(RenderGameOverlayEvent.Post event)
{
if(event.getType() != RenderGameOverlayEvent.ElementType.CHAT) return;
if(deadline() < System.currentTimeMillis()) return;
if(text()==EMPTY_TEXT) return;
String txt = text().getString();
if(txt.isEmpty()) return;
PoseStack mxs = event.getMatrixStack();
final Window win = mc.getWindow();
final Font fr = mc.font;
final boolean was_unicode = fr.isBidirectional();
final int cx = win.getGuiScaledWidth() / 2;
final int cy = (int)(win.getGuiScaledHeight() * overlay_y_);
final int w = fr.width(txt);
final int h = fr.lineHeight;
fillGradient(mxs, cx-(w/2)-3, cy-2, cx+(w/2)+2, cy+h+2, 0xaa333333, 0xaa444444);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy-2, 0xaa333333);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy+h+2, 0xaa333333);
vLine(mxs, cx-(w/2)-3, cy-2, cy+h+2, 0xaa333333);
vLine(mxs, cx+(w/2)+2, cy-2, cy+h+2, 0xaa333333);
drawCenteredString(mxs, fr, text(), cx , cy+1, 0x00ffaa00);
}
}
}

View file

@ -1,158 +1,158 @@
/*
* @file RfEnergy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General RF/FE energy handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import javax.annotation.Nullable;
public class RfEnergy
{
public static int feed(World world, BlockPos pos, @Nullable Direction side, int rf_energy)
{
final TileEntity te = world.getBlockEntity(pos);
if(te == null) return 0;
final IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, side).orElse(null);
if(es == null) return 0;
return es.receiveEnergy(rf_energy, false);
}
public static class Battery implements IEnergyStorage
{
protected int capacity_;
protected int charge_rate_;
protected int discharge_rate_;
protected int energy_;
public Battery(int capacity)
{ this(capacity, capacity); }
public Battery(int capacity, int transfer_rate)
{ this(capacity, transfer_rate, transfer_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate)
{ this(capacity, charge_rate, discharge_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate, int energy)
{
capacity_ = Math.max(capacity, 1);
charge_rate_ = MathHelper.clamp(charge_rate, 0, capacity_);
discharge_rate_ = MathHelper.clamp(discharge_rate, 0, capacity_);
energy_ = MathHelper.clamp(energy, 0, capacity_);
}
// ---------------------------------------------------------------------------------------------------
public Battery setMaxEnergyStored(int capacity)
{ capacity_ = Math.max(capacity, 1); return this; }
public Battery setEnergyStored(int energy)
{ energy_ = MathHelper.clamp(energy, 0, capacity_); return this; }
public Battery setChargeRate(int in_rate)
{ charge_rate_ = MathHelper.clamp(in_rate, 0, capacity_); return this; }
public Battery setDischargeRate(int out_rate)
{ discharge_rate_ = MathHelper.clamp(out_rate, 0, capacity_); return this; }
public int getChargeRate()
{ return charge_rate_; }
public int getDischargeRate()
{ return discharge_rate_; }
public boolean isEmpty()
{ return energy_ <= 0; }
public boolean isFull()
{ return energy_ >= capacity_; }
public int getSOC()
{ return (int)MathHelper.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); }
public int getComparatorOutput()
{ return (int)MathHelper.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); }
public boolean draw(int energy)
{
if(energy_ < energy) return false;
energy_ -= energy;
return true;
}
public boolean feed(int energy)
{
energy_ = Math.min(energy_+energy, capacity_);
return energy_ >= capacity_;
}
public Battery clear()
{ energy_ = 0; return this; }
public Battery load(CompoundNBT nbt, String key)
{ setEnergyStored(nbt.getInt(key)); return this; }
public Battery load(CompoundNBT nbt)
{ return load(nbt, "Energy"); }
public CompoundNBT save(CompoundNBT nbt, String key)
{ nbt.putInt(key, energy_); return nbt; }
public CompoundNBT save(CompoundNBT nbt)
{ return save(nbt, "Energy"); }
public LazyOptional<IEnergyStorage> createEnergyHandler()
{ return LazyOptional.of(() -> (IEnergyStorage)this); }
// IEnergyStorage ------------------------------------------------------------------------------------
@Override
public int receiveEnergy(int feed_energy, boolean simulate)
{
if(!canReceive()) return 0;
int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_-energy_);
if(!simulate) energy_ += e;
return e;
}
@Override
public int extractEnergy(int draw_energy, boolean simulate)
{
if(!canExtract()) return 0;
int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_);
if(!simulate) energy_ -= e;
return e;
}
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int getMaxEnergyStored()
{ return capacity_; }
@Override
public boolean canExtract()
{ return discharge_rate_ > 0; }
@Override
public boolean canReceive()
{ return charge_rate_ > 0; }
}
}
/*
* @file RfEnergy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General RF/FE energy handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import javax.annotation.Nullable;
public class RfEnergy
{
public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy)
{
final BlockEntity te = world.getBlockEntity(pos);
if(te == null) return 0;
final IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, side).orElse(null);
if(es == null) return 0;
return es.receiveEnergy(rf_energy, false);
}
public static class Battery implements IEnergyStorage
{
protected int capacity_;
protected int charge_rate_;
protected int discharge_rate_;
protected int energy_;
public Battery(int capacity)
{ this(capacity, capacity); }
public Battery(int capacity, int transfer_rate)
{ this(capacity, transfer_rate, transfer_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate)
{ this(capacity, charge_rate, discharge_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate, int energy)
{
capacity_ = Math.max(capacity, 1);
charge_rate_ = Mth.clamp(charge_rate, 0, capacity_);
discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_);
energy_ = Mth.clamp(energy, 0, capacity_);
}
// ---------------------------------------------------------------------------------------------------
public Battery setMaxEnergyStored(int capacity)
{ capacity_ = Math.max(capacity, 1); return this; }
public Battery setEnergyStored(int energy)
{ energy_ = Mth.clamp(energy, 0, capacity_); return this; }
public Battery setChargeRate(int in_rate)
{ charge_rate_ = Mth.clamp(in_rate, 0, capacity_); return this; }
public Battery setDischargeRate(int out_rate)
{ discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); return this; }
public int getChargeRate()
{ return charge_rate_; }
public int getDischargeRate()
{ return discharge_rate_; }
public boolean isEmpty()
{ return energy_ <= 0; }
public boolean isFull()
{ return energy_ >= capacity_; }
public int getSOC()
{ return (int)Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); }
public int getComparatorOutput()
{ return (int)Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); }
public boolean draw(int energy)
{
if(energy_ < energy) return false;
energy_ -= energy;
return true;
}
public boolean feed(int energy)
{
energy_ = Math.min(energy_+energy, capacity_);
return energy_ >= capacity_;
}
public Battery clear()
{ energy_ = 0; return this; }
public Battery load(CompoundTag nbt, String key)
{ setEnergyStored(nbt.getInt(key)); return this; }
public Battery load(CompoundTag nbt)
{ return load(nbt, "Energy"); }
public CompoundTag save(CompoundTag nbt, String key)
{ nbt.putInt(key, energy_); return nbt; }
public CompoundTag save(CompoundTag nbt)
{ return save(nbt, "Energy"); }
public LazyOptional<IEnergyStorage> createEnergyHandler()
{ return LazyOptional.of(() -> this); }
// IEnergyStorage ------------------------------------------------------------------------------------
@Override
public int receiveEnergy(int feed_energy, boolean simulate)
{
if(!canReceive()) return 0;
int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_-energy_);
if(!simulate) energy_ += e;
return e;
}
@Override
public int extractEnergy(int draw_energy, boolean simulate)
{
if(!canExtract()) return 0;
int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_);
if(!simulate) energy_ -= e;
return e;
}
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int getMaxEnergyStored()
{ return capacity_; }
@Override
public boolean canExtract()
{ return discharge_rate_ > 0; }
@Override
public boolean canReceive()
{ return charge_rate_ > 0; }
}
}

View file

@ -0,0 +1,45 @@
/*
* @file RsSignals.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General redstone signal related functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public class RsSignals
{
public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide)
{
return state.isSignalSource();
}
public static int fromContainer(@Nullable Container container)
{
if(container == null) return 0;
final double max = container.getMaxStackSize();
if(max <= 0) return 0;
boolean nonempty = false;
double fill_level = 0;
for(int i=0; i<container.getContainerSize(); ++i) {
ItemStack stack = container.getItem(i);
if(stack.isEmpty() || (stack.getMaxStackSize()<=0)) continue;
fill_level += ((double)stack.getCount()) / Math.min(max, stack.getMaxStackSize());
nonempty = true;
}
fill_level /= container.getContainerSize();
return (int)(Math.floor(fill_level * 14) + (nonempty?1:0)); // vanilla compliant calculation.
}
}

View file

@ -1,67 +1,66 @@
/*
* @file SidedProxy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General client/server sideness selection proxy.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
import net.minecraftforge.fml.DistExecutor;
import javax.annotation.Nullable;
import java.util.Optional;
public class SidedProxy
{
@Nullable
public static PlayerEntity getPlayerClientSide()
{ return proxy.getPlayerClientSide(); }
@Nullable
public static World getWorldClientSide()
{ return proxy.getWorldClientSide(); }
@Nullable
public static Minecraft mc()
{ return proxy.mc(); }
@Nullable
public static Optional<Boolean> isCtrlDown()
{ return proxy.isCtrlDown(); }
@Nullable
public static Optional<Boolean> isShiftDown()
{ return proxy.isShiftDown(); }
// --------------------------------------------------------------------------------------------------------
// @todo: check conditions for safeRunForDist()
private static ISidedProxy proxy = DistExecutor.unsafeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);
private interface ISidedProxy
{
default @Nullable PlayerEntity getPlayerClientSide() { return null; }
default @Nullable World getWorldClientSide() { return null; }
default @Nullable Minecraft mc() { return null; }
default Optional<Boolean> isCtrlDown() { return Optional.empty(); }
default Optional<Boolean> isShiftDown() { return Optional.empty(); }
}
private static final class ClientProxy implements ISidedProxy
{
public @Nullable PlayerEntity getPlayerClientSide() { return Minecraft.getInstance().player; }
public @Nullable World getWorldClientSide() { return Minecraft.getInstance().level; }
public @Nullable Minecraft mc() { return Minecraft.getInstance(); }
public Optional<Boolean> isCtrlDown() { return Optional.of(Auxiliaries.isCtrlDown()); }
public Optional<Boolean> isShiftDown() { return Optional.of(Auxiliaries.isShiftDown()); }
}
private static final class ServerProxy implements ISidedProxy
{
}
}
/*
* @file SidedProxy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General client/server sideness selection proxy.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraftforge.fml.DistExecutor;
import javax.annotation.Nullable;
import java.util.Optional;
public class SidedProxy
{
@Nullable
public static Player getPlayerClientSide()
{ return proxy.getPlayerClientSide(); }
@Nullable
public static Level getWorldClientSide()
{ return proxy.getWorldClientSide(); }
@Nullable
public static Minecraft mc()
{ return proxy.mc(); }
@Nullable
public static Optional<Boolean> isCtrlDown()
{ return proxy.isCtrlDown(); }
@Nullable
public static Optional<Boolean> isShiftDown()
{ return proxy.isShiftDown(); }
// --------------------------------------------------------------------------------------------------------
private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);
private interface ISidedProxy
{
default @Nullable Player getPlayerClientSide() { return null; }
default @Nullable Level getWorldClientSide() { return null; }
default @Nullable Minecraft mc() { return null; }
default Optional<Boolean> isCtrlDown() { return Optional.empty(); }
default Optional<Boolean> isShiftDown() { return Optional.empty(); }
}
private static final class ClientProxy implements ISidedProxy
{
public @Nullable Player getPlayerClientSide() { return Minecraft.getInstance().player; }
public @Nullable Level getWorldClientSide() { return Minecraft.getInstance().level; }
public @Nullable Minecraft mc() { return Minecraft.getInstance(); }
public Optional<Boolean> isCtrlDown() { return Optional.of(Auxiliaries.isCtrlDown()); }
public Optional<Boolean> isShiftDown() { return Optional.of(Auxiliaries.isShiftDown()); }
}
private static final class ServerProxy implements ISidedProxy
{
}
}

View file

@ -1,115 +1,118 @@
/*
* @file Tooltip.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Delayed tooltip for a selected area. Constructed with a
* GUI, invoked in `render()`.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.inventory.container.Container;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@OnlyIn(Dist.CLIENT)
public class TooltipDisplay
{
private static long default_delay = 800;
private static int default_max_deviation = 1;
public static void config(long delay, int max_deviation)
{
default_delay = MathHelper.clamp(delay, 500, 5000);
default_max_deviation = MathHelper.clamp(max_deviation, 1, 5);
}
// ---------------------------------------------------------------------------------------------------
public static class TipRange
{
public final int x0,y0,x1,y1;
public final Supplier<ITextComponent> text;
public TipRange(int x, int y, int w, int h, ITextComponent text)
{ this(x,y,w,h,()->text); }
public TipRange(int x, int y, int w, int h, Supplier<ITextComponent> text)
{ this.text=text; this.x0=x; this.y0=y; this.x1=x0+w-1; this.y1=y0+h-1; }
}
// ---------------------------------------------------------------------------------------------------
private List<TipRange> ranges = new ArrayList<>();
private long delay = default_delay;
private int max_deviation = default_max_deviation;
private int x_last, y_last;
private long t;
private static boolean had_render_exception = false;
public TooltipDisplay()
{ t = System.currentTimeMillis(); }
public void init(List<TipRange> ranges, long delay_ms, int max_deviation_xy)
{
this.ranges = ranges;
this.delay = delay_ms;
this.max_deviation = max_deviation_xy;
t = System.currentTimeMillis();
x_last = y_last = 0;
}
public void init(List<TipRange> ranges)
{ init(ranges, default_delay, default_max_deviation); }
public void init(TipRange... ranges)
{ init(Arrays.asList(ranges), default_delay, default_max_deviation); }
public void resetTimer()
{ t = System.currentTimeMillis(); }
public <T extends Container> boolean render(MatrixStack mx, final ContainerScreen<T> gui, int x, int y)
{
if(had_render_exception) return false;
if((Math.abs(x-x_last) > max_deviation) || (Math.abs(y-y_last) > max_deviation)) {
x_last = x;
y_last = y;
resetTimer();
return false;
} else if(Math.abs(System.currentTimeMillis()-t) < delay) {
return false;
} else if(ranges.stream().noneMatch(
(tip)->{
if((x<tip.x0) || (x>tip.x1) || (y<tip.y0) || (y>tip.y1)) return false;
String text = tip.text.get().getString();
if(!text.isEmpty() && (!text.startsWith("block."))) {
try {
gui.renderTooltip(mx, tip.text.get(), x, y);
} catch(Exception ex) {
had_render_exception = true;
Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'");
return false;
}
}
return true;
})
){
resetTimer();
return false;
} else {
return true;
}
}
}
/*
* @file Tooltip.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Delayed tooltip for a selected area. Constructed with a
* GUI, invoked in `render()`.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@OnlyIn(Dist.CLIENT)
public class TooltipDisplay
{
private static long default_delay = 450;
private static int default_max_deviation = 1;
public static void config(long delay, int max_deviation)
{
default_delay = Mth.clamp(delay, 500, 5000);
default_max_deviation = Mth.clamp(max_deviation, 1, 5);
}
// ---------------------------------------------------------------------------------------------------
public static class TipRange
{
public final int x0,y0,x1,y1;
public final Supplier<Component> text;
public TipRange(int x, int y, int w, int h, Component text)
{ this(x,y,w,h,()->text); }
public TipRange(int x, int y, int w, int h, Supplier<Component> text)
{ this.text=text; this.x0=x; this.y0=y; this.x1=x0+w-1; this.y1=y0+h-1; }
}
// ---------------------------------------------------------------------------------------------------
private List<TipRange> ranges = new ArrayList<>();
private long delay = default_delay;
private int max_deviation = default_max_deviation;
private int x_last, y_last;
private long t;
private static boolean had_render_exception = false;
public TooltipDisplay()
{ t = System.currentTimeMillis(); }
public TooltipDisplay init(List<TipRange> ranges, long delay_ms, int max_deviation_xy)
{
this.ranges = ranges;
this.delay = delay_ms;
this.max_deviation = max_deviation_xy;
t = System.currentTimeMillis();
x_last = y_last = 0;
return this;
}
public TooltipDisplay init(List<TipRange> ranges)
{ return init(ranges, default_delay, default_max_deviation); }
public TooltipDisplay init(TipRange... ranges)
{ return init(Arrays.asList(ranges), default_delay, default_max_deviation); }
public TooltipDisplay delay(int ms)
{ delay = (ms<=0) ? default_delay : ms; return this; }
public void resetTimer()
{ t = System.currentTimeMillis(); }
public <T extends AbstractContainerMenu> boolean render(PoseStack mx, final AbstractContainerScreen<T> gui, int x, int y)
{
if(had_render_exception) return false;
if((Math.abs(x-x_last) > max_deviation) || (Math.abs(y-y_last) > max_deviation)) {
x_last = x;
y_last = y;
resetTimer();
return false;
} else if(Math.abs(System.currentTimeMillis()-t) < delay) {
return false;
} else if(ranges.stream().noneMatch(
(tip)->{
if((x<tip.x0) || (x>tip.x1) || (y<tip.y0) || (y>tip.y1)) return false;
String text = tip.text.get().getString();
if(text.isEmpty()) return false;
try {
gui.renderTooltip(mx, tip.text.get(), x, y);
} catch(Exception ex) {
had_render_exception = true;
Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'");
return false;
}
return true;
})
){
resetTimer();
return false;
} else {
return true;
}
}
}

View file

@ -0,0 +1,119 @@
package wile.engineersdecor.libmc.ui;
import net.minecraft.util.Mth;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.function.BiConsumer;
public class Containers
{
// -------------------------------------------------------------------------------------------------------------------
// Slots
// -------------------------------------------------------------------------------------------------------------------
public static class StorageSlot extends Slot
{
protected BiConsumer<ItemStack, ItemStack> slot_change_action_ = (oldStack, newStack)->{};
protected int stack_limit_ = 64;
public boolean enabled = true;
public StorageSlot(Container inventory, int index, int x, int y)
{ super(inventory, index, x, y); }
public StorageSlot setSlotStackLimit(int limit)
{ stack_limit_ = Mth.clamp(limit, 1, 64); return this; }
public int getMaxStackSize()
{ return stack_limit_; }
public StorageSlot setSlotChangeNotifier(BiConsumer<ItemStack, ItemStack> action)
{ slot_change_action_ = action; return this; }
@Override
public void onQuickCraft(ItemStack oldStack, ItemStack newStack)
{ slot_change_action_.accept(oldStack, newStack);} // no crafting trigger
@Override
public void set(ItemStack stack)
{
if(stack.sameItem(getItem())) {
super.set(stack);
} else {
final ItemStack before = getItem().copy();
super.set(stack); // whatever this does else next to setting inventory.
slot_change_action_.accept(before, getItem());
}
}
@Override
public boolean mayPlace(ItemStack stack)
{ return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); }
@Override
public int getMaxStackSize(ItemStack stack)
{ return Math.min(getMaxStackSize(), stack_limit_); }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return enabled; }
}
public static class LockedSlot extends Slot
{
protected int stack_limit_ = 64;
public boolean enabled = true;
public LockedSlot(Container inventory, int index, int x, int y)
{ super(inventory, index, x, y); }
public LockedSlot setSlotStackLimit(int limit)
{ stack_limit_ = Mth.clamp(limit, 1, 64); return this; }
public int getMaxStackSize()
{ return stack_limit_; }
@Override
public int getMaxStackSize(ItemStack stack)
{ return Math.min(getMaxStackSize(), stack_limit_); }
@Override
public boolean mayPlace(ItemStack stack)
{ return false; }
@Override
public boolean mayPickup(Player player)
{ return false; }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return enabled; }
}
public static class HiddenSlot extends Slot
{
public HiddenSlot(Container inventory, int index)
{ super(inventory, index, 0, 0); }
@Override
public int getMaxStackSize(ItemStack stack)
{ return getMaxStackSize(); }
@Override
public boolean mayPlace(ItemStack stack)
{ return false; }
@Override
public boolean mayPickup(Player player)
{ return false; }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return false; }
}
}

View file

@ -0,0 +1,322 @@
package wile.engineersdecor.libmc.ui;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.TooltipDisplay;
import java.util.function.Consumer;
import java.util.function.Function;
public class Guis
{
// -------------------------------------------------------------------------------------------------------------------
// Gui base
// -------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static abstract class ContainerGui<T extends AbstractContainerMenu> extends AbstractContainerScreen<T>
{
protected final ResourceLocation background_image_;
protected final Player player_;
protected final Guis.BackgroundImage gui_background_;
protected final TooltipDisplay tooltip_ = new TooltipDisplay();
public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height)
{
super(menu, player_inv, title);
this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image);
this.player_ = player_inv.player;
this.imageWidth = width;
this.imageHeight = height;
gui_background_ = new Guis.BackgroundImage(background_image_, width, height, new Coord2d(0,0));
}
public ContainerGui(T menu, Inventory player_inv, Component title, String background_image)
{
super(menu, player_inv, title);
this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image);
this.player_ = player_inv.player;
gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, new Coord2d(0,0));
}
@Override
public void init()
{
super.init();
gui_background_.init(this, new Coord2d(0,0)).show();
}
@Override
public void render(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
renderBackground(mx);
super.render(mx, mouseX, mouseY, partialTicks);
if(!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY);
}
@Override
protected void renderLabels(PoseStack mx, int x, int y)
{}
@Override
@SuppressWarnings("deprecation")
protected final void renderBg(PoseStack mx, float partialTicks, int mouseX, int mouseY)
{
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
gui_background_.draw(mx, this);
renderBgWidgets(mx, partialTicks, mouseX, mouseY);
RenderSystem.disableBlend();
}
public final ResourceLocation getBackgroundImage()
{ return background_image_; }
protected void renderBgWidgets(PoseStack mx, float partialTicks, int mouseX, int mouseY)
{}
protected void renderItemTemplate(PoseStack mx, ItemStack stack, int x, int y)
{
final ItemRenderer ir = itemRenderer;
final int main_zl = getBlitOffset();
final float zl = ir.blitOffset;
final int x0 = getGuiLeft();
final int y0 = getGuiTop();
ir.blitOffset = -80;
ir.renderGuiItem(stack, x0+x, y0+y);
RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial();
RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest();
RenderSystem.defaultBlendFunc();
RenderSystem.enableBlend();
ir.blitOffset = zl;
setBlitOffset(100);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f);
RenderSystem.setShaderTexture(0, background_image_);
blit(mx, x0+x, y0+y, x, y, 16, 16);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
setBlitOffset(main_zl);
}
}
// -------------------------------------------------------------------------------------------------------------------
// Gui elements
// -------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class Coord2d
{
public final int x, y;
public Coord2d(int x, int y) { this.x=x; this.y=y; }
}
@OnlyIn(Dist.CLIENT)
public static class UiWidget extends net.minecraft.client.gui.components.AbstractWidget
{
protected static final Component EMPTY_TEXT = new TextComponent("");
protected static final Function<UiWidget,Component> NO_TOOLTIP = (uiw)->EMPTY_TEXT;
@SuppressWarnings("all") private Function<UiWidget,Component> tooltip_ = NO_TOOLTIP;
public UiWidget(int x, int y, int width, int height, Component title)
{ super(x, y, width, height, title); }
public UiWidget init(Screen parent)
{
this.x += ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiLeft() : 0);
this.y += ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiTop() : 0);
return this;
}
public UiWidget init(Screen parent, Coord2d position)
{
this.x = position.x + ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiLeft() : 0);
this.y = position.y + ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiTop() : 0);
return this;
}
public int getWidth()
{ return this.width; }
public int getHeight()
{ return this.height; }
public UiWidget show()
{ visible = true; return this; }
public UiWidget hide()
{ visible = false; return this; }
@Override
public void renderButton(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks)
{
super.renderButton(matrixStack, mouseX, mouseY, partialTicks);
if(isHovered()) renderToolTip(matrixStack, mouseX, mouseY);
}
@Override
@SuppressWarnings("all")
public void renderToolTip(PoseStack matrixStack, int mouseX, int mouseY)
{
if(tooltip_ == NO_TOOLTIP) return;
/// todo: need a Screen for that, not sure if adding a reference initialized in init() may cause GC problems.
}
@Override
public void updateNarration(NarrationElementOutput element_output)
{}
}
@OnlyIn(Dist.CLIENT)
public static class HorizontalProgressBar extends UiWidget
{
private final Coord2d texture_position_base_;
private final Coord2d texture_position_filled_;
private final ResourceLocation atlas_;
private double progress_max_ = 100;
private double progress_ = 0;
public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy)
{
super(0, 0, width, height, EMPTY_TEXT);
atlas_ = atlas;
texture_position_base_ = base_texture_xy;
texture_position_filled_ = filled_texture_xy;
}
public HorizontalProgressBar setProgress(double progress)
{ progress_ = Mth.clamp(progress, 0, progress_max_); return this; }
public double getProgress()
{ return progress_; }
public HorizontalProgressBar setMaxProgress(double progress)
{ progress_max_ = Math.max(progress, 0); return this; }
public double getMaxProgress()
{ return progress_max_; }
public HorizontalProgressBar show()
{ visible = true; return this; }
public HorizontalProgressBar hide()
{ visible = false; return this; }
@Override
public void playDownSound(SoundManager handler)
{}
@Override
protected void renderBg(PoseStack mx, Minecraft mc, int x, int y)
{}
@Override
public void renderButton(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
RenderSystem.setShaderTexture(0, atlas_);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
blit(mx, x, y, texture_position_base_.x, texture_position_base_.y, width, height);
if((progress_max_ > 0) && (progress_ > 0)) {
int w = Mth.clamp((int)Math.round((progress_ * width) / progress_max_), 0, width);
blit(mx, x, y, texture_position_filled_.x, texture_position_filled_.y, w, height);
}
}
}
@OnlyIn(Dist.CLIENT)
public static class BackgroundImage extends UiWidget
{
private final ResourceLocation atlas_;
private final Coord2d atlas_position_;
public boolean visible;
public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position)
{
super(0, 0, width, height, EMPTY_TEXT);
atlas_ = atlas;
atlas_position_ = atlas_position;
this.width = width;
this.height = height;
visible = true;
}
public void draw(PoseStack mx, Screen parent)
{
if(!visible) return;
RenderSystem.setShaderTexture(0, atlas_);
parent.blit(mx, x, y, atlas_position_.x, atlas_position_.y, width, height);
}
}
@OnlyIn(Dist.CLIENT)
public static class CheckBox extends UiWidget
{
private final Coord2d texture_position_off_;
private final Coord2d texture_position_on_;
private final ResourceLocation atlas_;
private boolean checked_ = false;
private Consumer<CheckBox> on_click_ = (checkbox)->{};
public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on)
{
super(0, 0, width, height, EMPTY_TEXT);
texture_position_off_ = atlas_texture_position_off;
texture_position_on_ = atlas_texture_position_on;
atlas_ = atlas;
}
public boolean checked()
{ return checked_; }
public CheckBox checked(boolean on)
{ checked_ = on; return this; }
public CheckBox onclick(Consumer<CheckBox> action)
{ on_click_ = action; return this; }
@Override
public void onClick(double mouseX, double mouseY)
{ checked_ = !checked_; on_click_.accept(this); }
@Override
public void renderButton(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, atlas_);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_;
blit(mx, x, y, pos.x, pos.y, width, height);
}
}
}