mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
Basic StationRocketEngineBlock functionality. StationFlightComputerBlock simply sets traveling to true. Rocket crash fixes. TEST
This commit is contained in:
@@ -1 +1,2 @@
|
||||
// 1.21.1 2026-01-11T15:05:33.587044 Tags for minecraft:fluid mod id aphelion
|
||||
// 1.21.1 2026-03-14T19:22:23.3145075 Tags for minecraft:fluid mod id aphelion
|
||||
36b33555f1ae6c80989afdd9d986ec0883959f49 data/minecraft/tags/fluid/rocket_fuel.json
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"aphelion:rocket_fuel"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.xevianlight.aphelion;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
@@ -127,14 +129,15 @@ public class Aphelion {
|
||||
@SubscribeEvent
|
||||
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
// ItemBlockRenderTypes.setRenderLayer(ModFluids.SOURCE_OIL_FLUID.get(), RenderType.translucent());
|
||||
// ItemBlockRenderTypes.setRenderLayer(ModFluids.FLOWING_OIL_FLUID.get(), RenderType.translucent());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModFluids.ROCKET_FUEL.get(), RenderType.translucent());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModFluids.FLOWING_ROCKET_FUEL.get(), RenderType.translucent());
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientExtensions(RegisterClientExtensionsEvent event) {
|
||||
event.registerFluidType(((BaseFluidType) ModFluidTypes.OIL_FLUID_TYPE.get()).getClientFluidTypeExtensions(), ModFluidTypes.OIL_FLUID_TYPE.get());
|
||||
event.registerFluidType(((BaseFluidType) ModFluidTypes.ROCKET_FUEL_FLUID_TYPE.get()).getClientFluidTypeExtensions(), ModFluidTypes.ROCKET_FUEL_FLUID_TYPE.get());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.xevianlight.aphelion.block.custom.base.BaseRocketContainer;
|
||||
import net.xevianlight.aphelion.block.custom.base.BaseRocketFuelTank;
|
||||
|
||||
public class BasicRocketContainer extends BaseRocketContainer {
|
||||
public BasicRocketContainer(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotCapacity() {
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicHorizontalEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationFlightComputerBlockEntity;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationFlightComputerBlock extends BasicHorizontalEntityBlock {
|
||||
|
||||
public static final MapCodec<StationFlightComputerBlock> CODEC = simpleCodec(StationFlightComputerBlock::new);
|
||||
|
||||
public StationFlightComputerBlock(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||
return new StationFlightComputerBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
if (level.getBlockEntity(pos) instanceof StationFlightComputerBlockEntity computerBE) {
|
||||
PartitionData data = computerBE.getData();
|
||||
if (data != null) {
|
||||
data.setTraveling(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.ItemInteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.common.SoundActions;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import net.neoforged.neoforge.fluids.FluidUtil;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.base.StationEngineBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationRocketEngineBlock extends StationEngineBlock {
|
||||
public StationRocketEngineBlock(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||
return new StationRocketEngineBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemInteractionResult useItemOn(
|
||||
ItemStack stack,
|
||||
BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit) {
|
||||
|
||||
// Only intercept on client if holding a fluid container, otherwise let block placement through
|
||||
if (level.isClientSide) {
|
||||
return FluidUtil.getFluidHandler(stack).isPresent()
|
||||
? ItemInteractionResult.SUCCESS
|
||||
: ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (!(be instanceof StationRocketEngineBlockEntity engineBE)) {
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
IFluidHandler tankHandler = level.getCapability(
|
||||
Capabilities.FluidHandler.BLOCK, pos, state, be, null
|
||||
);
|
||||
|
||||
if (tankHandler == null) {
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
|
||||
FluidStack fluidBeforeInteraction = FluidUtil.getFluidContained(player.getItemInHand(hand))
|
||||
.orElse(FluidStack.EMPTY);
|
||||
|
||||
boolean success = FluidUtil.interactWithFluidHandler(player, hand, tankHandler);
|
||||
|
||||
if (success) {
|
||||
FluidStack tankFluid = tankHandler.getFluidInTank(0);
|
||||
FluidStack relevantFluid = fluidBeforeInteraction.isEmpty() ? tankFluid : fluidBeforeInteraction;
|
||||
|
||||
Aphelion.LOGGER.info("fluidBeforeInteraction: {}", fluidBeforeInteraction);
|
||||
Aphelion.LOGGER.info("tankFluid: {}", tankFluid);
|
||||
Aphelion.LOGGER.info("relevantFluid: {}", relevantFluid);
|
||||
|
||||
if (!relevantFluid.isEmpty()) {
|
||||
FluidType fluidType = relevantFluid.getFluid().getFluidType();
|
||||
SoundEvent sound = fluidBeforeInteraction.isEmpty()
|
||||
? fluidType.getSound(SoundActions.BUCKET_FILL)
|
||||
: fluidType.getSound(SoundActions.BUCKET_EMPTY);
|
||||
|
||||
Aphelion.LOGGER.info("fluidType: {}", fluidType);
|
||||
Aphelion.LOGGER.info("sound: {}", sound);
|
||||
|
||||
if (sound != null) {
|
||||
Aphelion.LOGGER.info("Playing sound!");
|
||||
level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
return ItemInteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
|
||||
public class BaseRocketContainer extends Block implements IRocketInventoryUpgrade {
|
||||
public BaseRocketContainer(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.METAL)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotCapacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,10 @@ public abstract class BasicEntityBlock extends BaseEntityBlock {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties.of();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationEngineBlock extends BasicEntityBlock {
|
||||
|
||||
public static final MapCodec<StationEngineBlock> CODEC = simpleCodec(StationEngineBlock::new);
|
||||
|
||||
protected StationEngineBlock(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.util.Constants;
|
||||
|
||||
public class StationRocketEngineBlockEntity extends StationEngineBlockEntity {
|
||||
|
||||
/// Seconds to travel 1 AU
|
||||
private final double SECONDS_PER_AU = 60;
|
||||
/// AU per tick
|
||||
private final double SPEED = 1/(SECONDS_PER_AU*20);
|
||||
|
||||
@Override
|
||||
public double getTravelSpeed() {
|
||||
return SPEED;
|
||||
}
|
||||
|
||||
protected StationRocketEngineBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
|
||||
// TODO change type to ModBlockEntities.STATION_ROCKET_ENGINE_BLOCK_ENTITY.get()
|
||||
super(type, pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
super.serverTick(level, time, state, pos); // IMPORTANT!!!
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,8 @@ public interface TickableBlockEntity {
|
||||
* <p>Implementations should return {@code true} once initialization has finished
|
||||
* to prevent {@code firstTick} from running again.</p>
|
||||
*
|
||||
* <p>Returns {@code true} if not implemented.</p>
|
||||
*
|
||||
* @return {@code true} if initialization has completed, {@code false} otherwise
|
||||
*/
|
||||
default boolean isInitialized() {
|
||||
@@ -74,6 +76,8 @@ public interface TickableBlockEntity {
|
||||
* <p>Implementations should perform any required setup and ensure that
|
||||
* {@code isInitialized()} returns {@code true} afterward.</p>
|
||||
*
|
||||
* <p>Will never run if {@link #isInitialized()} is not implemented.</p>
|
||||
*
|
||||
* @param level the level the block entity exists in
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
|
||||
@@ -33,13 +33,14 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RocketAssemblerBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
Direction facing;
|
||||
BlockPos padScanStart = BlockPos.ZERO;
|
||||
private PadInfo padBounds;
|
||||
RocketEntity lastRocket;
|
||||
|
||||
private @Nullable PartitionData data;
|
||||
|
||||
@@ -296,14 +297,17 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
if (level == null) return null;
|
||||
RocketStructure structure = scan();
|
||||
RocketEntity rocket = null;
|
||||
if (lastRocket != null)
|
||||
lastRocket.disassemble();
|
||||
|
||||
var rockets = getRocketsInPad();
|
||||
if (rockets.size() == 1) rocket = rockets.getFirst();
|
||||
if (rocket != null)
|
||||
rocket.disassemble();
|
||||
|
||||
if (structure != null && seatPos != null) {
|
||||
RocketStructure.clearCaptured(level, seatPos, structure);
|
||||
rocket = RocketEntity.spawnRocket(level, seatPos, structure);
|
||||
Aphelion.LOGGER.info("Spawn rocket result: {}", rocket);
|
||||
}
|
||||
lastRocket = rocket;
|
||||
return rocket;
|
||||
}
|
||||
|
||||
@@ -373,6 +377,38 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
|
||||
return s.is(TOWER_BLOCK);
|
||||
}
|
||||
|
||||
public @NotNull List<RocketEntity> getRocketsInPad() {
|
||||
if (level == null || padBounds == null) return List.of();
|
||||
|
||||
AABB padBox = new AABB(
|
||||
padBounds.min().getX(),
|
||||
padBounds.min().getY(),
|
||||
padBounds.min().getZ(),
|
||||
padBounds.max().getX() + 1,
|
||||
padBounds.max().getY() + 1,
|
||||
padBounds.max().getZ() + 1
|
||||
);
|
||||
|
||||
var rockets = new ArrayList<>(level.getEntitiesOfClass(RocketEntity.class, padBox.inflate(0.2)));
|
||||
|
||||
List<RocketEntity> found = new java.util.ArrayList<>(List.of());
|
||||
|
||||
for (RocketEntity rocket : rockets) {
|
||||
AABB rocketBox = rocket.getBoundingBox();
|
||||
if (!isFullyInside(padBox, rocketBox)) continue;
|
||||
|
||||
found.add(rocket);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private static boolean isFullyInside(AABB outer, AABB inner) {
|
||||
return inner.minX >= outer.minX && inner.maxX <= outer.maxX
|
||||
&& inner.minY >= outer.minY && inner.maxY <= outer.maxY
|
||||
&& inner.minZ >= outer.minZ && inner.maxZ <= outer.maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(@NotNull CompoundTag tag, HolderLookup.@NotNull Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModDimensions;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class StationFlightComputerBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
public StationFlightComputerBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.STATION_FLIGHT_COMPUTER_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
protected PartitionData data;
|
||||
private boolean isInitialized = false;
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
if (data == null) return;
|
||||
data.setTraveling(true);
|
||||
}
|
||||
|
||||
public @Nullable PartitionData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
if (level.isClientSide()) return;
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
|
||||
}
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
protected boolean setTraveling(boolean value) {
|
||||
if (data == null) return false;
|
||||
data.setTraveling(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
|
||||
import net.xevianlight.aphelion.block.entity.custom.base.StationEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModFluidTags;
|
||||
|
||||
public class StationRocketEngineBlockEntity extends StationEngineBlockEntity {
|
||||
|
||||
/// Seconds to travel 1 AU
|
||||
private final double SECONDS_PER_AU = 60;
|
||||
/// AU per tick
|
||||
private final double SPEED = 1/(SECONDS_PER_AU*20);
|
||||
/// Fuel consumption per tick in millibuckets
|
||||
private static final int FUEL_CONSUMPTION = 10;
|
||||
|
||||
private FluidTank tank = new FluidTank(
|
||||
2000,
|
||||
fluidStack -> fluidStack.is(ModFluidTags.ROCKET_FUEL)
|
||||
);
|
||||
|
||||
@Override
|
||||
public double getTravelSpeed() {
|
||||
return SPEED;
|
||||
}
|
||||
|
||||
public IFluidHandler getFluidStorage(Direction direction) {
|
||||
return tank;
|
||||
}
|
||||
|
||||
public StationRocketEngineBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.STATION_ROCKET_ENGINE_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
super.serverTick(level, time, state, pos);
|
||||
burn();
|
||||
}
|
||||
|
||||
private void burn() {
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (data.getDestination() != null && data.isTraveling()) {
|
||||
if (!tank.isEmpty() && tank.getFluid().is(ModFluidTags.ROCKET_FUEL) && tank.getFluidAmount() >= FUEL_CONSUMPTION) { // has enough fuel?
|
||||
if (data.travel(getTravelSpeed()))
|
||||
tank.drain(FUEL_CONSUMPTION, IFluidHandler.FluidAction.EXECUTE);
|
||||
} else {
|
||||
// not enough fuel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
tag.put("fluid", tank.writeToNBT(registries, new CompoundTag()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.loadAdditional(tag, registries);
|
||||
if (tag.contains("fluid")) {
|
||||
tank.readFromNBT(registries, tag.getCompound("fluid"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
saveAdditional(tag, registries);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
package net.xevianlight.aphelion.block.entity.custom.base;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@@ -7,6 +7,7 @@ import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModDimensions;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
@@ -16,7 +17,7 @@ import javax.annotation.Nullable;
|
||||
public abstract class StationEngineBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
private boolean isInitialized = false;
|
||||
private @Nullable PartitionData data;
|
||||
protected @Nullable PartitionData data;
|
||||
|
||||
/**
|
||||
* The travel speed in AU/tick.
|
||||
@@ -32,31 +33,9 @@ public abstract class StationEngineBlockEntity extends BlockEntity implements Ti
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles station travel logic for this engine.
|
||||
*
|
||||
* <p>If the associated station is currently traveling, this method advances
|
||||
* its movement using the value returned by {@link #getTravelSpeed()}.</p>
|
||||
*
|
||||
* <p>Subclasses may override this method to add additional server-side behavior.
|
||||
* When doing so, {@code super.serverTick(...)} should be called to preserve
|
||||
* the default travel logic.</p>
|
||||
*
|
||||
* <p>This method is invoked once per server tick.</p>
|
||||
*
|
||||
* @param level the server level the block entity exists in
|
||||
* @param time the current tick time offset used for scheduling
|
||||
* @param state the current block state
|
||||
* @param pos the world position of the block entity
|
||||
*/
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
double speed = getTravelSpeed();
|
||||
if (data != null) {
|
||||
if (data.isTraveling()) {
|
||||
data.travel(speed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,6 +49,7 @@ public abstract class StationEngineBlockEntity extends BlockEntity implements Ti
|
||||
if (level instanceof ServerLevel serverLevel) {
|
||||
if (serverLevel.dimension() == ModDimensions.SPACE) {
|
||||
data = SpacePartitionSavedData.get(serverLevel).getDataForBlockPos(pos);
|
||||
data.addEngine(pos);
|
||||
}
|
||||
}
|
||||
isInitialized = true;
|
||||
@@ -49,11 +49,16 @@ public class AphelionDebugOverlay {
|
||||
// Left side of F3
|
||||
event.getLeft().add("");
|
||||
event.getLeft().add("Aphelion:");
|
||||
event.getLeft().add(" Orbit: " + orbitId);
|
||||
event.getLeft().add(" Orbit: " + PartitionClientState.lastData().getOrbit());
|
||||
// event.getLeft().add(" Sky: " + rendererSummary);
|
||||
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
|
||||
event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination());
|
||||
event.getLeft().add(" Station Owner:" + PartitionClientState.lastData().getOwner());
|
||||
event.getLeft().add(" Station Destination: " + PartitionClientState.lastData().getDestination());
|
||||
event.getLeft().add(" Station Owner: " + PartitionClientState.lastData().getOwner());
|
||||
event.getLeft().add(" Station Engines: " + PartitionClientState.lastData().getEngines().toArray().length);
|
||||
event.getLeft().add(" Station Landing Pads: " + PartitionClientState.lastData().getLandingPadContollersAsArray().length);
|
||||
event.getLeft().add(" Station Traveling: " + PartitionClientState.lastData().isTraveling());
|
||||
event.getLeft().add(" Station Trip Distance AU: " + PartitionClientState.lastData().getTripDistanceAU());
|
||||
event.getLeft().add(" Station Distance Traveled AU: " + PartitionClientState.lastData().getDistanceTraveledAU());
|
||||
var server = mc.getSingleplayerServer();
|
||||
ServerLevel singlePlayerLevel;
|
||||
if (server != null) {
|
||||
|
||||
@@ -75,12 +75,10 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
|
||||
|
||||
// int px = PartitionClientState.pxOr(0);
|
||||
// int py = PartitionClientState.pyOr(0);
|
||||
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
if (data != null) return data;
|
||||
return ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.VAFMultiblockDummyBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.*;
|
||||
|
||||
@@ -51,4 +52,14 @@ public class ModBlockEntities {
|
||||
BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of(
|
||||
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<StationRocketEngineBlockEntity>> STATION_ROCKET_ENGINE_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("station_rocket_engine_block_entity", () -> BlockEntityType.Builder.of(
|
||||
StationRocketEngineBlockEntity::new, ModBlocks.STATION_ROCKET_ENGINE.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<StationFlightComputerBlockEntity>> STATION_FLIGHT_COMPUTER_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("station_flight_computer_block_entity", () -> BlockEntityType.Builder.of(
|
||||
StationFlightComputerBlockEntity::new, ModBlocks.STATION_FLIGHT_COMPUTER_BLOCK.get()).build(null)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock;
|
||||
public class ModBlocks {
|
||||
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(Aphelion.MOD_ID);
|
||||
|
||||
public static final DeferredBlock<Block> TEST_BLOCK = BLOCKS.register("test_block", () -> new TestBlock(TestBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> BLOCK_STEEL = BLOCKS.register("block_steel", () -> new BlockSteel(BlockSteel.getProperties()));
|
||||
public static final DeferredBlock<Block> LAUNCH_PAD = BLOCKS.register("launch_pad", () -> new LaunchPad(LaunchPad.getProperties()));
|
||||
public static final DeferredBlock<Block> DIMENSION_CHANGER = BLOCKS.register("dimension_changer", () -> new DimensionChangerBlock(DimensionChangerBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ELECTRIC_ARC_FURNACE = BLOCKS.register("electric_arc_furnace", () -> new ElectricArcFurnace(ElectricArcFurnace.getProperties()));
|
||||
public static final DeferredBlock<Block> ARC_FURNACE_CASING_BLOCK = BLOCKS.register("arc_furnace_casing", () -> new ArcFurnaceCasingBlock(ArcFurnaceCasingBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> VACUUM_ARC_FURNACE_CONTROLLER = BLOCKS.register("vacuum_arc_furnace_controller", () -> new VacuumArcFurnaceController(VacuumArcFurnaceController.getProperties()));
|
||||
public static final DeferredBlock<Block> VAF_MULTIBLOCK_DUMMY_BLOCK = BLOCKS.register("vaf_dummy_block", () -> new VAFMultiblockDummyBlock(VAFMultiblockDummyBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> TEST_BLOCK = BLOCKS.register("test_block", () -> new TestBlock(TestBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> BLOCK_STEEL = BLOCKS.register("block_steel", () -> new BlockSteel(BlockSteel.getProperties()));
|
||||
public static final DeferredBlock<Block> LAUNCH_PAD = BLOCKS.register("launch_pad", () -> new LaunchPad(LaunchPad.getProperties()));
|
||||
public static final DeferredBlock<Block> DIMENSION_CHANGER = BLOCKS.register("dimension_changer", () -> new DimensionChangerBlock(DimensionChangerBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ELECTRIC_ARC_FURNACE = BLOCKS.register("electric_arc_furnace", () -> new ElectricArcFurnace(ElectricArcFurnace.getProperties()));
|
||||
public static final DeferredBlock<Block> ARC_FURNACE_CASING_BLOCK = BLOCKS.register("arc_furnace_casing", () -> new ArcFurnaceCasingBlock(ArcFurnaceCasingBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> VACUUM_ARC_FURNACE_CONTROLLER = BLOCKS.register("vacuum_arc_furnace_controller", () -> new VacuumArcFurnaceController(VacuumArcFurnaceController.getProperties()));
|
||||
public static final DeferredBlock<Block> VAF_MULTIBLOCK_DUMMY_BLOCK = BLOCKS.register("vaf_dummy_block", () -> new VAFMultiblockDummyBlock(VAFMultiblockDummyBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> OXYGEN_TEST_BLOCK = BLOCKS.register("oxygen_test_block", () -> new OxygenTestBlock(OxygenTestBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_ASSEMBLER = BLOCKS.register("rocket_assembler", () -> new RocketAssembler(RocketAssembler.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_SEAT = BLOCKS.register("rocket_seat", () -> new RocketSeat(RocketSeat.getProperties()));
|
||||
public static final DeferredBlock<Block> STATION_ROCKET_ENGINE = BLOCKS.register("station_rocket_engine", () -> new StationRocketEngineBlock(StationRocketEngineBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> BASIC_ROCKET_FUEL_TANK = BLOCKS.register("basic_rocket_fuel_tank", () -> new BasicRocketFuelTank(BasicRocketFuelTank.getProperties()));
|
||||
public static final DeferredBlock<Block> BASIC_ROCKET_CONTAINER = BLOCKS.register("basic_rocket_container", () -> new BasicRocketContainer(BasicRocketContainer.getProperties()));
|
||||
public static final DeferredBlock<Block> STATION_FLIGHT_COMPUTER_BLOCK = BLOCKS.register("station_flight_computer", () -> new StationFlightComputerBlock(StationFlightComputerBlock.getProperties()));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class ModCreativeTabs {
|
||||
output.accept(ModItems.INGOT_NEODYMIUM);
|
||||
output.accept(ModItems.INGOT_IRIDIUM);
|
||||
output.accept(ModFluids.OIL_BUCKET);
|
||||
output.accept(ModFluids.ROCKET_FUEL_BUCKET);
|
||||
output.accept(ModItems.MUSIC_DISC_BIT_SHIFT);
|
||||
}).build());
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.xevianlight.aphelion.core.init;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
|
||||
public class ModFluidTags {
|
||||
public static final TagKey<Fluid> ROCKET_FUEL = create("rocket_fuel");
|
||||
|
||||
private static TagKey<Fluid> create(String name) {
|
||||
return TagKey.create(Registries.FLUID, ResourceLocation.withDefaultNamespace(name));
|
||||
}
|
||||
|
||||
public static TagKey<Fluid> create(ResourceLocation name) {
|
||||
return TagKey.create(Registries.FLUID, name);
|
||||
}
|
||||
}
|
||||
@@ -38,5 +38,8 @@ public static final DeferredItem<Item> MUSIC_DISC_BIT_SHIFT = ITEMS.register("mu
|
||||
public static final DeferredItem<BlockItem> LAUNCH_PAD = ITEMS.register("launch_pad", () -> new BlockItem(ModBlocks.LAUNCH_PAD.get(), LaunchPad.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> ROCKET_ASSEMBLER = ITEMS.register("rocket_assembler", () -> new BlockItem(ModBlocks.ROCKET_ASSEMBLER.get(), RocketAssembler.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> ROCKET_SEAT = ITEMS.register("rocket_seat", () -> new BlockItem(ModBlocks.ROCKET_SEAT.get(), RocketSeat.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> STATION_ROCKET_ENGINE = ITEMS.register("station_rocket_engine", () -> new BlockItem(ModBlocks.STATION_ROCKET_ENGINE.get(), StationRocketEngineBlock.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> BASIC_ROCKET_FUEL_TANK = ITEMS.register("basic_rocket_fuel_tank", () -> new BlockItem(ModBlocks.BASIC_ROCKET_FUEL_TANK.get(), BasicRocketFuelTank.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> BASIC_ROCKET_CONTAINER = ITEMS.register("basic_rocket_container", () -> new BlockItem(ModBlocks.BASIC_ROCKET_CONTAINER.get(), BasicRocketContainer.getItemProperties()));
|
||||
// public static final DeferredItem<BlockItem> VAF_MULTIBLOCK_DUMMY_BLOCK = ITEMS.register("vaf_multiblock_dummy_block", () -> new BlockItem(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), VAFMultiblockDummyBlock.getItemProperties()));
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class SpacePartitionSavedData extends SavedData {
|
||||
e.putBoolean("Traveling", pd.isTraveling());
|
||||
|
||||
e.putDouble("DistanceTraveled", pd.getDistanceTraveledAU());
|
||||
e.putDouble("DistanceToDest", pd.getTripDistanceAU());
|
||||
e.putDouble("DistanceToDest", pd.recalculateTripDistAU());
|
||||
|
||||
if (pd.getOwner() != null) {
|
||||
e.putUUID("Owner", pd.getOwner());
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.core.UUIDUtil;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||
import net.xevianlight.aphelion.util.BigCodec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -26,6 +27,11 @@ public class PartitionData {
|
||||
private UUID owner;
|
||||
private List<BlockPos> landingPadControllers;
|
||||
private List<BlockPos> engines;
|
||||
private double currentOrbitDistanceAU;
|
||||
|
||||
public PartitionData() {
|
||||
|
||||
}
|
||||
|
||||
public PartitionData(@Nullable ResourceLocation orbit) {
|
||||
this.orbit = orbit;
|
||||
@@ -36,7 +42,7 @@ public class PartitionData {
|
||||
this.generated = false;
|
||||
this.owner = null;
|
||||
this.landingPadControllers = List.of();
|
||||
this.engines = List.of();
|
||||
this.engines = new ArrayList<>(List.of());
|
||||
}
|
||||
|
||||
public PartitionData(PartitionData other) {
|
||||
@@ -68,7 +74,7 @@ public class PartitionData {
|
||||
PartitionData::getDistanceTraveledAU,
|
||||
|
||||
ByteBufCodecs.DOUBLE,
|
||||
PartitionData::getTripDistanceAU,
|
||||
PartitionData::recalculateTripDistAU,
|
||||
|
||||
ByteBufCodecs.optional(UUIDUtil.STREAM_CODEC),
|
||||
d -> Optional.ofNullable(d.getOwner()),
|
||||
@@ -102,6 +108,8 @@ public class PartitionData {
|
||||
|
||||
public void setOrbit(@Nullable ResourceLocation orbit) {
|
||||
this.orbit = orbit;
|
||||
recalculateTripDistAU();
|
||||
distanceTraveledAU = 0;
|
||||
}
|
||||
|
||||
public @Nullable ResourceLocation getDestination() {
|
||||
@@ -110,6 +118,8 @@ public class PartitionData {
|
||||
|
||||
public void setDestination(@Nullable ResourceLocation destination) {
|
||||
this.destination = destination;
|
||||
recalculateTripDistAU();
|
||||
distanceTraveledAU = 0;
|
||||
}
|
||||
|
||||
public boolean isTraveling() {
|
||||
@@ -128,6 +138,19 @@ public class PartitionData {
|
||||
this.distanceTraveledAU = distanceTraveledAU;
|
||||
}
|
||||
|
||||
public double recalculateTripDistAU() {
|
||||
var currentPlanet = PlanetCache.getByOrbitOrNull(orbit);
|
||||
if (currentPlanet == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var destPlanet = PlanetCache.getOrDefault(destination);
|
||||
|
||||
var dist = destPlanet.orbitDistance() - currentPlanet.orbitDistance();
|
||||
this.tripDistanceAU = dist;
|
||||
return dist;
|
||||
}
|
||||
|
||||
public double getTripDistanceAU() {
|
||||
return tripDistanceAU;
|
||||
}
|
||||
@@ -146,9 +169,20 @@ public class PartitionData {
|
||||
* distance is set to exactly {@code tripDistanceAU}.</p>
|
||||
*
|
||||
* @param distance the distance to advance in astronomical units (AU)
|
||||
* @return {@code true} when we arrive at our destination, {@code false} otherwise.
|
||||
*/
|
||||
public void travel(double distance) {
|
||||
distanceTraveledAU = Math.min(distanceTraveledAU + distance, tripDistanceAU);
|
||||
public boolean travel(double distance) {
|
||||
if (distanceTraveledAU + distance > tripDistanceAU) {
|
||||
distanceTraveledAU = tripDistanceAU;
|
||||
var destinationPlanet = PlanetCache.getOrNull(destination);
|
||||
if (destinationPlanet != null) {
|
||||
setOrbit(destinationPlanet.orbit().location());
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
distanceTraveledAU += distance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isGenerated() {
|
||||
@@ -223,14 +257,40 @@ public class PartitionData {
|
||||
return landingPadControllers.remove(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a defensive copy of the world positions of all engines tracked by this partition.
|
||||
*
|
||||
* <p>This method returns only the stored {@link BlockPos} locations of known engines,
|
||||
* not the engine instances or their corresponding block entities. To interact with an
|
||||
* engine, retrieve the block entity from the world using the returned positions.</p>
|
||||
*
|
||||
* <p>It is not guaranteed that an engine exists at every returned position. If changes
|
||||
* fail to synchronize with this partition, the stored data may become inaccurate.
|
||||
* Always verify that the block entity at a given position is an engine before use.</p>
|
||||
*
|
||||
* <p>The returned list is a defensive copy and may be modified without affecting the
|
||||
* underlying partition data. To persist changes, use {@code setEngines(...)}.</p>
|
||||
*
|
||||
* @return a mutable list containing the tracked engine positions
|
||||
*/
|
||||
public List<BlockPos> getEngines() {
|
||||
return engines;
|
||||
return new ArrayList<>(engines);
|
||||
}
|
||||
|
||||
public void setEngines(List<BlockPos> engines) {
|
||||
this.engines = engines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an engine at the specified world position.
|
||||
*
|
||||
* <p>If an engine does not already exist at the given position, it is added
|
||||
* to the internal collection and the method returns {@code true}. If an engine
|
||||
* is already present at that position, no changes are made.</p>
|
||||
*
|
||||
* @param pos the world position of the engine to add
|
||||
* @return {@code true} if the engine was added, {@code false} if it already existed
|
||||
*/
|
||||
public boolean addEngine(BlockPos pos) {
|
||||
if (!engines.contains(pos)) {
|
||||
engines.add(pos);
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.data.tags.FluidTagsProvider;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.neoforged.neoforge.common.data.ExistingFileHelper;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.init.ModFluidTags;
|
||||
import net.xevianlight.aphelion.fluid.ModFluids;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,5 +23,7 @@ public class ModFluidTagsProvider extends FluidTagsProvider {
|
||||
// tag(FluidTags.LAVA)
|
||||
// .add(ModFluids.SOURCE_OIL_FLUID.get())
|
||||
// .add(ModFluids.FLOWING_OIL_FLUID.get());
|
||||
tag(ModFluidTags.ROCKET_FUEL)
|
||||
.add(ModFluids.ROCKET_FUEL.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn {
|
||||
|
||||
@@ -108,7 +109,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static RocketEntity spawnRocket(Level level, BlockPos pos, RocketStructure structure) {
|
||||
if (level.isClientSide) return null;
|
||||
|
||||
@@ -122,12 +122,22 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
0.0f
|
||||
);
|
||||
|
||||
rocket.setStructure(structure);
|
||||
level.addFreshEntity(rocket);
|
||||
// rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
|
||||
// rocket.INVENTORY.setSize(rocket.INVENTORY.getSlots() + 1);
|
||||
// rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
|
||||
|
||||
rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
|
||||
rocket.INVENTORY.setSize(rocket.INVENTORY.getSlots() + 1);
|
||||
rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
|
||||
// Fully initialize structure and containers
|
||||
rocket.setStructure(structure);
|
||||
|
||||
if (rocket.INVENTORY.getSlots() > 0)
|
||||
rocket.INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND), false);
|
||||
if (rocket.FUEL_TANK.getCapacity() != 0)
|
||||
rocket.FUEL_TANK.setFluid(new FluidStack(ModFluids.OIL.get(), 1000));
|
||||
if (rocket.FLUID_STORAGE.getCapacity() != 0)
|
||||
rocket.FLUID_STORAGE.setFluid(FluidStack.EMPTY);
|
||||
|
||||
|
||||
level.addFreshEntity(rocket);
|
||||
|
||||
return rocket;
|
||||
}
|
||||
@@ -166,6 +176,9 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
if (!level().isClientSide) {
|
||||
|
||||
if (INVENTORY.getSlots() > 0)
|
||||
INVENTORY.insertItem(0, new ItemStack(Items.DIAMOND, 1), false);
|
||||
|
||||
switch (getPhase()) {
|
||||
case IDLE, LANDED -> tickIdle();
|
||||
case PREPARE -> tickPrepare();
|
||||
@@ -184,7 +197,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
}
|
||||
|
||||
move(MoverType.SELF, getDeltaMovement());
|
||||
// Catch ANY edge cases which may cause a crash trying to move an entity as its chunk is unloading
|
||||
if (!this.isRemoved() && this.isAlive() && level().hasChunkAt(blockPosition())) {
|
||||
move(MoverType.SELF, getDeltaMovement());
|
||||
}
|
||||
}
|
||||
|
||||
private void tickIdle() {
|
||||
@@ -393,7 +409,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
if (tag.contains("RocketStructure")) {
|
||||
CompoundTag rocketTag = tag.getCompound("RocketStructure");
|
||||
structure.load(rocketTag);
|
||||
recalculateCapacitiesFromStructure();
|
||||
// recalculateCapacitiesFromStructure();
|
||||
|
||||
// Immediately apply correct bbox on load (server + client)
|
||||
double x = getX(), y = getY(), z = getZ();
|
||||
@@ -590,6 +606,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
public boolean disassemble() {
|
||||
if (level().isClientSide) return false;
|
||||
if (!(level() instanceof ServerLevel server)) return false;
|
||||
Aphelion.LOGGER.info("Disassemble called for rocket: " + getId());
|
||||
|
||||
// In rare instances we can disassemble a rocket AFTER it has been killed.
|
||||
// This usually only happens if another class instance still has a reference to this object stored and calls rocketEntity.disassemble().
|
||||
|
||||
@@ -8,13 +8,13 @@ import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.registration.HandlerThread;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.StationRocketEngineBlockEntity;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.BaseMultiblockDummyBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModEntities;
|
||||
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.network.RocketPayloadHandlers;
|
||||
import net.xevianlight.aphelion.network.PartitionPayloadHandler;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
@@ -25,15 +25,20 @@ public class ModBusEvents {
|
||||
@SubscribeEvent
|
||||
public static void registerCapabilities(RegisterCapabilitiesEvent event) {
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.TEST_BLOCK_ENTITY.get(), TestBlockEntity::getItemHandler);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getEnergyStorage);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getEnergyStorage);
|
||||
// event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), VAFMultiblockDummyBlockEntity::getItemHandler);
|
||||
|
||||
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getItemHandler);
|
||||
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), BaseMultiblockDummyBlockEntity::getEnergyStorage);
|
||||
|
||||
event.registerEntity(Capabilities.ItemHandler.ENTITY, ModEntities.ROCKET.get(), (rocket, ctx) -> rocket.getInventory());
|
||||
|
||||
event.registerBlockEntity(Capabilities.FluidHandler.BLOCK, ModBlockEntities.STATION_ROCKET_ENGINE_BLOCK_ENTITY.get(), StationRocketEngineBlockEntity::getFluidStorage);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -7,7 +7,11 @@ import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.FogRenderer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
|
||||
import net.neoforged.neoforge.common.SoundAction;
|
||||
import net.neoforged.neoforge.common.SoundActions;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -53,6 +57,17 @@ public class BaseFluidType extends FluidType {
|
||||
this.fogEnd = fogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SoundEvent getSound(SoundAction action) {
|
||||
if (action == SoundActions.BUCKET_FILL) {
|
||||
return SoundEvents.BUCKET_FILL;
|
||||
}
|
||||
if (action == SoundActions.BUCKET_EMPTY) {
|
||||
return SoundEvents.BUCKET_EMPTY;
|
||||
}
|
||||
return super.getSound(action);
|
||||
}
|
||||
|
||||
public IClientFluidTypeExtensions getClientFluidTypeExtensions() {
|
||||
return new IClientFluidTypeExtensions() {
|
||||
@Override
|
||||
|
||||
@@ -8,23 +8,51 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModFluidTypes {
|
||||
public static final ResourceLocation WATER_STILL_RL = ResourceLocation.parse("block/water_still");
|
||||
public static final ResourceLocation WATER_FLOWING_RL = ResourceLocation.parse("block/water_flow");
|
||||
public static final ResourceLocation WATER_OVERLAY_RL = ResourceLocation.parse("block/water_overlay");
|
||||
public static final ResourceLocation WATER_STILL_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_still");
|
||||
|
||||
public static final ResourceLocation WATER_FLOWING_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_flow");
|
||||
|
||||
public static final ResourceLocation WATER_OVERLAY_RL =
|
||||
ResourceLocation.fromNamespaceAndPath("minecraft", "block/water_overlay");
|
||||
|
||||
static final Color oilColor = new Color(10, 10, 10, 255);
|
||||
static final Color rocketFuelColor = new Color(73, 59, 28, 255);
|
||||
|
||||
public static final DeferredRegister<FluidType> FLUID_TYPES =
|
||||
DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_TYPES, Aphelion.MOD_ID);
|
||||
|
||||
public static final Supplier<FluidType> OIL_FLUID_TYPE = registerFluidType("oil",
|
||||
new BaseFluidType(WATER_STILL_RL, WATER_FLOWING_RL, WATER_OVERLAY_RL, 0xFF101010,
|
||||
new Vector3f(10f / 255f, 10f / 255f, 10f / 255f),
|
||||
FluidType.Properties.create().canDrown(true), 0f, 2f
|
||||
new BaseFluidType(
|
||||
WATER_STILL_RL,
|
||||
WATER_FLOWING_RL,
|
||||
WATER_OVERLAY_RL,
|
||||
oilColor.getRGB(),
|
||||
colToVec(oilColor),
|
||||
FluidType.Properties.create().canDrown(true),
|
||||
0f,
|
||||
0.5f
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
public static final Supplier<FluidType> ROCKET_FUEL_FLUID_TYPE = registerFluidType("rocket_fuel",
|
||||
new BaseFluidType(
|
||||
WATER_STILL_RL,
|
||||
WATER_FLOWING_RL,
|
||||
WATER_OVERLAY_RL,
|
||||
rocketFuelColor.getRGB(),
|
||||
colToVec(rocketFuelColor),
|
||||
FluidType.Properties.create().canDrown(true),
|
||||
0f,
|
||||
2f)
|
||||
);
|
||||
|
||||
private static Supplier<FluidType> registerFluidType(String name, FluidType fluidType) {
|
||||
return FLUID_TYPES.register(name, () -> fluidType);
|
||||
}
|
||||
@@ -32,4 +60,8 @@ public class ModFluidTypes {
|
||||
public static void register(IEventBus eventBus) {
|
||||
FLUID_TYPES.register(eventBus);
|
||||
}
|
||||
|
||||
public static Vector3f colToVec (Color color) {
|
||||
return new Vector3f(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,29 @@ public class ModFluids {
|
||||
public static final DeferredRegister<Fluid> FLUIDS =
|
||||
DeferredRegister.create(BuiltInRegistries.FLUID, Aphelion.MOD_ID);
|
||||
|
||||
public static final Supplier<FlowingFluid> ROCKET_FUEL = FLUIDS.register("rocket_fuel",
|
||||
() -> new BaseFlowingFluid.Source(ModFluids.ROCKET_FUEL_PROPERTIES));
|
||||
public static final Supplier<FlowingFluid> FLOWING_ROCKET_FUEL = FLUIDS.register("flowing_rocket_fuel",
|
||||
() -> new BaseFlowingFluid.Flowing(ModFluids.ROCKET_FUEL_PROPERTIES));
|
||||
|
||||
public static final DeferredBlock<LiquidBlock> ROCKET_FUEL_BLOCK = ModBlocks.BLOCKS.register("rocket_fuel",
|
||||
() -> new LiquidBlock(ModFluids.ROCKET_FUEL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
|
||||
public static final DeferredItem<Item> ROCKET_FUEL_BUCKET = ModItems.ITEMS.registerItem("rocket_fuel_bucket",
|
||||
properties -> new BucketItem(ModFluids.ROCKET_FUEL.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
|
||||
|
||||
public static final BaseFlowingFluid.Properties ROCKET_FUEL_PROPERTIES = new BaseFlowingFluid.Properties(
|
||||
ModFluidTypes.ROCKET_FUEL_FLUID_TYPE, ROCKET_FUEL, FLOWING_ROCKET_FUEL)
|
||||
.slopeFindDistance(2).levelDecreasePerBlock(2).tickRate(10)
|
||||
.block(ModFluids.ROCKET_FUEL_BLOCK).bucket(ModFluids.ROCKET_FUEL_BUCKET);
|
||||
|
||||
public static final Supplier<FlowingFluid> OIL = FLUIDS.register("oil",
|
||||
() -> new BaseFlowingFluid.Source(ModFluids.OIL_PROPERTIES));
|
||||
public static final Supplier<FlowingFluid> FLOWING_OIL = FLUIDS.register("flowing_oil",
|
||||
() -> new BaseFlowingFluid.Flowing(ModFluids.OIL_PROPERTIES));
|
||||
|
||||
public static final DeferredBlock<LiquidBlock> OIL_BLOCK = ModBlocks.BLOCKS.register("oil",
|
||||
() -> new LiquidBlock(ModFluids.OIL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
() -> new LiquidBlock(ModFluids.OIL.get(), BlockBehaviour.Properties.ofFullCopy(Blocks.WATER).noLootTable()));
|
||||
|
||||
public static final DeferredItem<Item> OIL_BUCKET = ModItems.ITEMS.registerItem("oil_bucket",
|
||||
properties -> new BucketItem(ModFluids.OIL.get(), properties.stacksTo(1).craftRemainder(Items.BUCKET)));
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
||||
import net.xevianlight.aphelion.util.Constants;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
12
src/main/java/net/xevianlight/aphelion/planet/Orbit.java
Normal file
12
src/main/java/net/xevianlight/aphelion/planet/Orbit.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.xevianlight.aphelion.planet;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
public record Orbit(
|
||||
int temp
|
||||
) {
|
||||
public static final Codec<Orbit> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
Codec.INT.fieldOf("temp").forGetter(Orbit::temp)
|
||||
).apply(inst, Orbit::new));
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
|
||||
public record Planet(
|
||||
public record Planet (
|
||||
ResourceKey<Level> dimension,
|
||||
ResourceKey<Orbit> orbit,
|
||||
double orbitDistance,
|
||||
ResourceKey<StarSystem> system,
|
||||
boolean oxygen,
|
||||
@@ -16,6 +17,7 @@ public record Planet(
|
||||
) {
|
||||
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
|
||||
ResourceKey.codec(ModRegistries.ORBIT).fieldOf("orbit").forGetter(Planet::orbit),
|
||||
Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance),
|
||||
ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system),
|
||||
Codec.BOOL.fieldOf("oxygen").forGetter(Planet::oxygen),
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -17,6 +18,7 @@ public final class PlanetCache {
|
||||
|
||||
public static final Planet DEFAULT = new Planet(
|
||||
ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")),
|
||||
ResourceKey.create(ModRegistries.ORBIT, Aphelion.id("orbit/overworld")),
|
||||
1,
|
||||
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")),
|
||||
true,
|
||||
@@ -48,6 +50,17 @@ public final class PlanetCache {
|
||||
return PLANETS.getOrDefault(id, DEFAULT);
|
||||
}
|
||||
|
||||
public static @Nullable Planet getOrNull(ResourceLocation id) {
|
||||
return PLANETS.getOrDefault(id, null);
|
||||
}
|
||||
|
||||
public static @Nullable Planet getByOrbitOrNull(ResourceLocation id) {
|
||||
return PLANETS.values().stream()
|
||||
.filter(planet -> planet.orbit().location().equals(id))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static Planet getByDimensionOrNull(ResourceKey<Level> dimension) {
|
||||
ResourceLocation planetId = PLANET_BY_DIMENSION.get(dimension);
|
||||
return planetId == null ? null : PLANETS.get(planetId);
|
||||
|
||||
@@ -3,12 +3,14 @@ package net.xevianlight.aphelion.util.registries;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.planet.Orbit;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.planet.StarSystem;
|
||||
|
||||
public class ModRegistries {
|
||||
public static final ResourceKey<Registry<StarSystem>> STAR_SYSTEM = createRegistryKey("star_system");
|
||||
public static final ResourceKey<Registry<Planet>> PLANET = createRegistryKey("planet");
|
||||
public static final ResourceKey<Registry<Orbit>> ORBIT = createRegistryKey("orbit");
|
||||
|
||||
private static <T> ResourceKey<Registry<T>> createRegistryKey(String name) {
|
||||
return ResourceKey.createRegistryKey(Aphelion.id(name));
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"custom_clouds": false,
|
||||
"custom_sky": false,
|
||||
"custom_weather": false,
|
||||
"dimension": "minecraft:overworld",
|
||||
"has_fog": true,
|
||||
"has_thick_fog": false,
|
||||
"render_in_rain": true,
|
||||
"sunrise_angle": 45,
|
||||
"sunrise_color": 14180147,
|
||||
"horizon_height": -128,
|
||||
"clear_color_scale": 1.0
|
||||
}
|
||||
8
src/main/resources/data/aphelion/planet/mars.json
Normal file
8
src/main/resources/data/aphelion/planet/mars.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dimension": "aphelion:mars",
|
||||
"orbit": "aphelion:orbit/mars",
|
||||
"orbit_distance": 1.5,
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": false
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"dimension": "minecraft:overworld",
|
||||
"orbit": "aphelion:orbit/overworld",
|
||||
"orbit_distance": 1,
|
||||
"star_system": "aphelon:sol",
|
||||
"star_system": "aphelion:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": false
|
||||
}
|
||||
Reference in New Issue
Block a user