mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 10:00:54 +01:00
Compare commits
17 Commits
oxygen-sys
...
gravity-sy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3154d32d79 | ||
|
|
aaa55b087f | ||
|
|
d9bd26e8a0 | ||
|
|
010fc7307f | ||
|
|
9356c43ea7 | ||
|
|
fa41966de4 | ||
|
|
3b5d10f414 | ||
|
|
331da7c78f | ||
|
|
74731444ea | ||
|
|
df344034a6 | ||
|
|
84000f31fd | ||
|
|
012985441f | ||
|
|
cc93d2fb42 | ||
|
|
2f0c499fdf | ||
|
|
9b030d8c9d | ||
|
|
b012528247 | ||
|
|
f3bd3f891a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,4 +23,4 @@ run
|
||||
runs
|
||||
run-data
|
||||
|
||||
repo
|
||||
repo
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// 1.21.1 2026-01-28T08:47:15.3186366 Loot Tables
|
||||
// 1.21.1 2026-02-06T10:36:07.5023829 Loot Tables
|
||||
69d8318ddba171526d1fabb87d9d93548ed8598e data/aphelion/loot_table/blocks/arc_furnace_casing.json
|
||||
05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json
|
||||
ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json
|
||||
b63130d9c10485676303d729807b6fcaac080294 data/aphelion/loot_table/blocks/electric_arc_furnace.json
|
||||
086c97543700ccc2f815da4e6c29f11159766889 data/aphelion/loot_table/blocks/gravity_test_block.json
|
||||
b9cfe672ead8e2673a7b2f5c4cec831e7e8e7040 data/aphelion/loot_table/blocks/launch_pad.json
|
||||
afb6519a03415b8e0d5bafc9fadb70905a398046 data/aphelion/loot_table/blocks/oxygen_test_block.json
|
||||
2c6748b2cfb5b78e0cc95a2f3583d4e50cb4c964 data/aphelion/loot_table/blocks/rocket_assemblerblock.json
|
||||
1ab50c99e9f478840b9d003fd56ebdcab12fbbce data/aphelion/loot_table/blocks/test_block.json
|
||||
7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json
|
||||
797bf9839d79e08b4832c9eaf3cb303b0471ed0c data/aphelion/loot_table/blocks/vaf_dummy_block.json
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// 1.21.1 2026-01-23T22:32:50.7214756 Tags for minecraft:block mod id aphelion
|
||||
// 1.21.1 2026-01-26T19:04:46.4981336 Tags for minecraft:block mod id aphelion
|
||||
46f0160a007d32a06624ad98f25e8a1a8d01bb08 data/aphelion/tags/block/launch_pad.json
|
||||
058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks.json
|
||||
058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks/steel.json
|
||||
7d420216f15b8f78d2a3b298f9bb773a9e5f79c3 data/minecraft/tags/block/mineable/pickaxe.json
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// 1.21.1 2026-01-28T08:47:15.3176368 Block States: aphelion
|
||||
// 1.21.1 2026-02-06T10:36:07.5013847 Block States: aphelion
|
||||
851ff42f7b21dec86107c8e0cefb3934ae4ebc08 assets/aphelion/blockstates/block_steel.json
|
||||
30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json
|
||||
cb4287104006c80c8396b290ab5258df65d62cef assets/aphelion/blockstates/electric_arc_furnace.json
|
||||
1975ab6c73f76642f8b6cfed00e0739b4ea5775f assets/aphelion/blockstates/gravity_test_block.json
|
||||
28131a570d3666b7f323de4ad8a69e52ceec92e2 assets/aphelion/blockstates/oxygen_test_block.json
|
||||
b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json
|
||||
a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json
|
||||
2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json
|
||||
5f7e8674070f31a63875b5d6147153bfa0eef61a assets/aphelion/models/block/electric_arc_furnace.json
|
||||
ad49034f318c6bc14a4844cf598c6999296aa8a0 assets/aphelion/models/block/gravity_test_block.json
|
||||
746f23f150a01524ad03cbd1eb822bfbb7cf453b assets/aphelion/models/block/oxygen_test_block.json
|
||||
e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json
|
||||
cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json
|
||||
3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json
|
||||
db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json
|
||||
279080c06ada87f54fd0a7b885b256dbe25a946a assets/aphelion/models/item/electric_arc_furnace.json
|
||||
8de1f7597e1fa82c5603a88040ad935aa1cb9b29 assets/aphelion/models/item/gravity_test_block.json
|
||||
24cf60e70f7d9450b0e70cf017662e80971bae17 assets/aphelion/models/item/oxygen_test_block.json
|
||||
74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json
|
||||
88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "aphelion:block/gravity_test_block"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "aphelion:block/gravity_test_block"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "aphelion:block/gravity_test_block"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}
|
||||
],
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "aphelion:gravity_test_block"
|
||||
}
|
||||
],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "aphelion:blocks/gravity_test_block"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}
|
||||
],
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "aphelion:launch_pad"
|
||||
}
|
||||
],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "aphelion:blocks/launch_pad"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}
|
||||
],
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "minecraft:air"
|
||||
}
|
||||
],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "aphelion:blocks/rocket_assemblerblock"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"aphelion:launch_pad"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
package net.xevianlight.aphelion;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
|
||||
import net.neoforged.neoforge.event.AddReloadListenerEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.block.dummy.renderer.MultiblockDummyRenderer;
|
||||
import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer;
|
||||
import net.xevianlight.aphelion.client.AphelionConfig;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.planet.AphelionPlanetJSONLoader;
|
||||
import net.xevianlight.aphelion.core.init.*;
|
||||
import net.xevianlight.aphelion.fluid.BaseFluidType;
|
||||
import net.xevianlight.aphelion.fluid.ModFluidTypes;
|
||||
import net.xevianlight.aphelion.fluid.ModFluids;
|
||||
import net.xevianlight.aphelion.recipe.ModRecipes;
|
||||
import net.xevianlight.aphelion.screen.ElectricArcFurnaceScreen;
|
||||
import net.xevianlight.aphelion.screen.ModMenuTypes;
|
||||
import net.xevianlight.aphelion.screen.TestBlockScreen;
|
||||
import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen;
|
||||
import net.xevianlight.aphelion.screen.*;
|
||||
import org.slf4j.Logger;
|
||||
import net.xevianlight.aphelion.entites.vehicles.RocketRenderer;
|
||||
|
||||
@@ -132,7 +136,7 @@ public class Aphelion {
|
||||
@SubscribeEvent
|
||||
public static void registerBER(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), MultiblockDummyRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
||||
// event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -140,11 +144,18 @@ public class Aphelion {
|
||||
event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new);
|
||||
event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new);
|
||||
event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new);
|
||||
event.register(ModMenuTypes.GRAVITY_TEST_BLOCK_MENU.get(), GravityTestBlockScreen::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(ClientTickEvent.Post e) {
|
||||
if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return;
|
||||
EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 50000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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.minecraft.world.phys.BlockHitResult;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GravityTestBlock extends BasicEntityBlock {
|
||||
|
||||
public GravityTestBlock(Properties properties) {super(properties, true);}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BaseEntityBlock> codec() {return null;}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return new GravityTestBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {return Properties.of();}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
if (state.getBlock() != newState.getBlock()) {
|
||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof GravityTestBlockEntity BE) {
|
||||
BE.removeGravityArea();
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) {
|
||||
|
||||
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof GravityTestBlockEntity testBlockEntity) {
|
||||
serverPlayer.openMenu(new SimpleMenuProvider(testBlockEntity, Component.literal("Gravity Test Block")), pos);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
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.xevianlight.aphelion.util.ModTags;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LaunchPad extends Block {
|
||||
|
||||
public static final BooleanProperty NORTH = BlockStateProperties.NORTH;
|
||||
public static final BooleanProperty EAST = BlockStateProperties.EAST;
|
||||
public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH;
|
||||
public static final BooleanProperty WEST = BlockStateProperties.WEST;
|
||||
|
||||
public LaunchPad(Properties properties) {
|
||||
super(properties);
|
||||
this.registerDefaultState(this.stateDefinition.any()
|
||||
.setValue(NORTH, false)
|
||||
.setValue(EAST, false)
|
||||
.setValue(SOUTH, false)
|
||||
.setValue(WEST, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(NORTH, EAST, SOUTH, WEST);
|
||||
}
|
||||
|
||||
private static boolean isPad(BlockState st) {
|
||||
return st.is(ModTags.Blocks.LAUNCH_PAD); // <- make a tag, recommended
|
||||
}
|
||||
|
||||
private BlockState withConnections(LevelAccessor level, BlockPos pos, BlockState state) {
|
||||
return state
|
||||
.setValue(NORTH, isPad(level.getBlockState(pos.north())))
|
||||
.setValue(EAST, isPad(level.getBlockState(pos.east())))
|
||||
.setValue(SOUTH, isPad(level.getBlockState(pos.south())))
|
||||
.setValue(WEST, isPad(level.getBlockState(pos.west())));
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.STONE)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
|
||||
return withConnections(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockState updateShape(@NotNull BlockState state, Direction dir, @NotNull BlockState neighborState,
|
||||
@NotNull LevelAccessor level, @NotNull BlockPos pos, @NotNull BlockPos neighborPos) {
|
||||
if (dir.getAxis().isHorizontal()) {
|
||||
return withConnections(level, pos, state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,53 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OxygenTestBlock extends Block implements EntityBlock {
|
||||
public class OxygenTestBlock extends BasicEntityBlock {
|
||||
|
||||
public OxygenTestBlock(Properties properties) {
|
||||
super(properties);
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
public static final MapCodec<OxygenTestBlock> CODEC = simpleCodec(OxygenTestBlock::new);
|
||||
|
||||
@Override
|
||||
protected @NotNull MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
|
||||
return new OxygenTestBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public RenderShape getRenderShape(@NotNull BlockState pState) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
if (state.getBlock() != newState.getBlock()) {
|
||||
BlockEntity blockEntity= level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof OxygenTestBlockEntity oxygenTestBlockEntity) {
|
||||
oxygenTestBlockEntity.removeEnclosed();
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
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.DirectionProperty;
|
||||
import net.xevianlight.aphelion.block.custom.base.BasicHorizontalEntityBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.RocketAssemblerBlockEntity;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RocketAssemblerBlock extends BasicHorizontalEntityBlock {
|
||||
|
||||
public RocketAssemblerBlock(Properties properties) {
|
||||
super(properties, true);
|
||||
}
|
||||
|
||||
public static final MapCodec<RocketAssemblerBlock> CODEC = simpleCodec(RocketAssemblerBlock::new);
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
public static Properties getProperties() {
|
||||
return Properties
|
||||
.of()
|
||||
.sound(SoundType.METAL)
|
||||
.destroyTime(2f)
|
||||
.explosionResistance(10f)
|
||||
.requiresCorrectToolForDrops();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return new RocketAssemblerBlockEntity(blockPos, blockState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BasicEntityBlock extends BaseEntityBlock {
|
||||
|
||||
private final boolean shouldTick;
|
||||
private BlockEntityType<?> entity;
|
||||
|
||||
protected BasicEntityBlock(Properties properties, boolean shouldTick) {
|
||||
super(properties);
|
||||
this.shouldTick = shouldTick;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull RenderShape getRenderShape(@NotNull BlockState state) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
public static Item.Properties getItemProperties() {
|
||||
return new Item.Properties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(@NotNull Level level, @NotNull BlockState state, @NotNull BlockEntityType<T> blockEntityType) {
|
||||
return !shouldTick ? null : (entityLevel, pos, blockState, blockEntity) -> {
|
||||
if (blockEntity instanceof TickableBlockEntity tickable) {
|
||||
long time = level.getGameTime() - pos.hashCode();
|
||||
tickable.tick(entityLevel, time, blockState, pos);
|
||||
if (level.isClientSide()) {
|
||||
tickable.clientTick((ClientLevel) level, time, state, pos);
|
||||
} else {
|
||||
tickable.serverTick((ServerLevel) level, time, state, pos);
|
||||
}
|
||||
if (!tickable.isInitialized()) tickable.firstTick(level, state, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, @NotNull Level level, @NotNull BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
if (state.getBlock() != newState.getBlock()) {
|
||||
if (level.getBlockEntity(pos) instanceof TickableBlockEntity tickable) {
|
||||
tickable.onRemoved();
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
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.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.DirectionProperty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BasicHorizontalEntityBlock extends BasicEntityBlock {
|
||||
|
||||
protected BasicHorizontalEntityBlock(Properties properties, boolean shouldTick) {
|
||||
super(properties, shouldTick);
|
||||
this.registerDefaultState(this.getStateDefinition().any());
|
||||
}
|
||||
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
|
||||
@Override
|
||||
protected @NotNull BlockState rotate(BlockState state, Rotation rotation) {
|
||||
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull BlockState mirror(BlockState state, Mirror mirror) {
|
||||
return state.rotate(mirror.getRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.xevianlight.aphelion.block.custom.base;
|
||||
|
||||
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.state.BlockState;
|
||||
|
||||
public interface TickableBlockEntity {
|
||||
void tick(Level entityLevel, long time, BlockState blockState, BlockPos pos);
|
||||
|
||||
void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos);
|
||||
|
||||
void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos);
|
||||
|
||||
default boolean isInitialized() {
|
||||
return true;
|
||||
};
|
||||
|
||||
void firstTick(Level level, BlockState state, BlockPos pos);
|
||||
|
||||
default void onRemoved() {}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult
|
||||
// Force rerender on client
|
||||
if (level != null) {
|
||||
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
|
||||
requestModelDataUpdate(); // if you rely on model data
|
||||
requestModelDataUpdate(); // if you rely on model partitionData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult
|
||||
setChanged();
|
||||
if (level != null) {
|
||||
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
|
||||
requestModelDataUpdate(); // only if you use model data
|
||||
requestModelDataUpdate(); // only if you use model partitionData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
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.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.init.ModBlocks;
|
||||
import net.xevianlight.aphelion.core.saveddata.GravitySavedData;
|
||||
import net.xevianlight.aphelion.screen.ElectricArcFurnaceMenu;
|
||||
import net.xevianlight.aphelion.screen.GravityTestBlockMenu;
|
||||
import net.xevianlight.aphelion.systems.GravityService;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GravityTestBlockEntity extends BlockEntity implements TickableBlockEntity, MenuProvider {
|
||||
|
||||
public GravityTestBlockEntity(BlockPos pos, BlockState blockState) {super(ModBlockEntities.GRAVITY_TEST_BLOCK_ENTITY.get(), pos, blockState);}
|
||||
|
||||
public int areaSize = 5;
|
||||
public float gravityStrength = 5f;
|
||||
private boolean isInitialized = false;
|
||||
public final ItemStackHandler inventory = new ItemStackHandler(0);
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) {
|
||||
pTag.putInt("areaSize", areaSize);
|
||||
pTag.putFloat("gravityStrength", gravityStrength);
|
||||
super.saveAdditional(pTag, pRegistries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAdditional(CompoundTag pTag, HolderLookup.Provider pRegistries) {
|
||||
areaSize = pTag.getInt("areaSize");
|
||||
gravityStrength = pTag.getFloat("gravityStrength");
|
||||
super.loadAdditional(pTag, pRegistries);
|
||||
}
|
||||
|
||||
public void setRadius(float r) {
|
||||
areaSize = (int) r;
|
||||
}
|
||||
|
||||
public void setStrength(float s) {
|
||||
gravityStrength = s;
|
||||
}
|
||||
|
||||
public void addGravityArea() {
|
||||
Level level = getLevel();
|
||||
if (level != null && level.isClientSide()) return;
|
||||
|
||||
BlockPos pos = this.getBlockPos();
|
||||
|
||||
GravityService.setGravityArea((ServerLevel) level, pos, gravityStrength, areaSize);
|
||||
}
|
||||
|
||||
public void removeGravityArea() {
|
||||
Level level = getLevel();
|
||||
if (level != null && level.isClientSide()) return;
|
||||
|
||||
BlockPos pos = this.getBlockPos();
|
||||
|
||||
GravityService.removeGravityArea((ServerLevel) level, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(Level entityLevel, long time, BlockState blockState, BlockPos pos) {}
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) { addGravityArea(); isInitialized = true; }
|
||||
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
|
||||
return new GravityTestBlockMenu(i, inventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void sendUpdate() {
|
||||
removeGravityArea();
|
||||
addGravityArea();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundBlockEntityDataPacket getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||
return this.saveCustomOnly(registries);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,38 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
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 static net.xevianlight.aphelion.Aphelion.LOGGER;
|
||||
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.util.FloodFill3D;
|
||||
import org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class OxygenTestBlockEntity extends BlockEntity {
|
||||
public class OxygenTestBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
|
||||
public OxygenTestBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
public boolean isInitialized;
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return this.isInitialized;
|
||||
}
|
||||
|
||||
private LevelChunk lastChunk;
|
||||
private int lastChunkX = Integer.MIN_VALUE, lastChunkZ = Integer.MIN_VALUE;
|
||||
|
||||
@@ -43,11 +52,25 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
return level != null && fastBlockState(pos).isAir();
|
||||
}
|
||||
|
||||
public static final int MAX_RANGE = 100;
|
||||
public static final int MAX_RANGE = 16;
|
||||
public boolean isInRange(BlockPos pos1, BlockPos pos2) {
|
||||
return Math.abs(pos1.getX() - pos2.getX()) + Math.abs(pos1.getY() - pos2.getY()) + Math.abs(pos1.getZ() - pos2.getZ()) <= MAX_RANGE;
|
||||
}
|
||||
|
||||
public void removeEnclosed() {
|
||||
if (enclosedCache != null) {
|
||||
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||
if (singleplayerServer != null) {
|
||||
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||
if (serverLevel != null) {
|
||||
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||
enclosedCache = Set.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 256*256*256 grid of booleans
|
||||
private class BigBoolGrid {
|
||||
int bitsSize;
|
||||
@@ -94,7 +117,7 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
// wrapping order is X->Z->Y, so we go along the X axis,
|
||||
// wrap to the Z axis to make a square, and once the square is full
|
||||
// we step up once along the Y axis.
|
||||
int bitPos = inX & 7; // bottom 4 bits of X is bit pos
|
||||
int bitPos = inX & 15; // bottom 4 bits of X is bit pos
|
||||
int bit = (1 << bitPos);
|
||||
|
||||
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y
|
||||
@@ -106,22 +129,29 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
}
|
||||
}
|
||||
|
||||
private List<BlockPos> enclosedCache;
|
||||
public List<BlockPos> getEnclosedBlocks() {
|
||||
if (level == null) return List.of();
|
||||
if (enclosedCache != null) return enclosedCache;
|
||||
private Set<BlockPos> enclosedCache;
|
||||
|
||||
|
||||
public Set<BlockPos> getEnclosedCache() {
|
||||
return enclosedCache;
|
||||
}
|
||||
|
||||
public Set<BlockPos> getEnclosedBlocks() {
|
||||
if (level == null) return Set.of();
|
||||
// if (enclosedCache != null) return enclosedCache;
|
||||
|
||||
long start = System.nanoTime();
|
||||
List<BlockPos> enclosedBlocks = new ArrayList<>();
|
||||
// make this bitch BIIIIIG
|
||||
BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
||||
Set<BlockPos> enclosedBlocks = FloodFill3D.run(level, getBlockPos(), 6000, FloodFill3D.TEST_FULL_SEAL, true);
|
||||
|
||||
// It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
||||
// maybe a bit more, IDK how exactly it scales to blocks.
|
||||
Stack<BlockPos> stack = new Stack<>();
|
||||
Stack<Integer> radiusStack = new Stack<>();
|
||||
|
||||
stack.add(this.getBlockPos());
|
||||
// // make this bitch BIIIIIG
|
||||
// BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
||||
//
|
||||
// // It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
||||
// // maybe a bit more, IDK how exactly it scales to blocks.
|
||||
// Stack<BlockPos> stack = new Stack<>();
|
||||
// Stack<Integer> radiusStack = new Stack<>();
|
||||
//
|
||||
// stack.add(this.getBlockPos());
|
||||
|
||||
// Do flood fill out from this block
|
||||
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest).
|
||||
@@ -130,21 +160,48 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
// and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
|
||||
|
||||
|
||||
BlockPos ourPos = getBlockPos();
|
||||
while (!stack.isEmpty()) {
|
||||
BlockPos spreadFromPos = stack.pop();
|
||||
for (Direction d : Direction.values()) {
|
||||
BlockPos relativePos = spreadFromPos.relative(d);
|
||||
// BlockPos ourPos = getBlockPos();
|
||||
// while (!stack.isEmpty()) {
|
||||
// BlockPos spreadFromPos = stack.pop();
|
||||
// for (Direction d : Direction.values()) {
|
||||
// BlockPos relativePos = spreadFromPos.relative(d);
|
||||
//
|
||||
// if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
||||
// // seen.add runs seen.contains under the hood,
|
||||
// // + this is the most expensive operation.
|
||||
// // should save a lot of time!
|
||||
// if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
||||
// enclosedBlocks.add(relativePos);
|
||||
// stack.add(relativePos);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
||||
// seen.add runs seen.contains under the hood,
|
||||
// + this is the most expensive operation.
|
||||
// should save a lot of time!
|
||||
if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
||||
enclosedBlocks.add(relativePos);
|
||||
stack.add(relativePos);
|
||||
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||
if (singleplayerServer != null) {
|
||||
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||
if (serverLevel != null) {
|
||||
|
||||
// Build a set of longs for the newly computed blocks (order-independent)
|
||||
boolean changed = isChanged(enclosedBlocks);
|
||||
// boolean changed = false;
|
||||
if (changed) {
|
||||
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||
|
||||
// Revert old affected area back to defaults
|
||||
if (enclosedCache != null) {
|
||||
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||
}
|
||||
|
||||
// Apply oxygen to new affected area
|
||||
savedData.setOxygen(serverLevel, enclosedBlocks, true);
|
||||
|
||||
LOGGER.info("Saved data for {} blocks to leveldata", enclosedBlocks.size());
|
||||
}
|
||||
|
||||
// Update the cache no matter what (so next compare is correct)
|
||||
enclosedCache = enclosedBlocks;
|
||||
}
|
||||
}
|
||||
long durationNanos = System.nanoTime() - start;
|
||||
@@ -152,12 +209,62 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
|
||||
|
||||
enclosedCache = enclosedBlocks;
|
||||
return enclosedBlocks;
|
||||
|
||||
return enclosedCache;
|
||||
}
|
||||
|
||||
private boolean isChanged(Set<BlockPos> enclosedBlocks) {
|
||||
LongOpenHashSet newSet = new LongOpenHashSet(enclosedBlocks.size());
|
||||
for (BlockPos p : enclosedBlocks) {
|
||||
newSet.add(p.asLong());
|
||||
}
|
||||
|
||||
// Build a set of longs for the cached blocks (if any)
|
||||
LongOpenHashSet oldSet = null;
|
||||
if (enclosedCache != null) {
|
||||
oldSet = new LongOpenHashSet(enclosedCache.size());
|
||||
for (BlockPos p : enclosedCache) {
|
||||
oldSet.add(p.asLong());
|
||||
}
|
||||
}
|
||||
|
||||
// Only save if the set of affected blocks has changed
|
||||
boolean changed = (oldSet == null) || !oldSet.equals(newSet);
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void helper() {
|
||||
var myVar = new BlockPos(1, 1, 1).hashCode();
|
||||
}
|
||||
|
||||
int ticks = 0;
|
||||
int refreshAfter = 20;
|
||||
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState blockState, BlockPos pos) {
|
||||
ticks++;
|
||||
if (ticks >= refreshAfter) {
|
||||
getEnclosedBlocks();
|
||||
ticks = 0;
|
||||
|
||||
// UNCOMMENT FOR DEBUG ONLY!!! EXTREMELY TPS INTENSIVE!!!
|
||||
// EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(Level entityLevel, long time, BlockState blockState, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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.entity.BlockEntityType;
|
||||
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.util.RocketStructure;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
public class RocketAssemblerBlockEntity extends BlockEntity implements TickableBlockEntity {
|
||||
|
||||
public boolean isInitialized;
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
public RocketAssemblerBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModBlockEntities.ROCKET_ASSEMBLER_BLOCK_ENTITY.get(), pos, blockState);
|
||||
}
|
||||
|
||||
public void tick(Level level1, BlockPos blockPos, BlockState blockState) {
|
||||
|
||||
}
|
||||
|
||||
public boolean getPlatform() {
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public RocketStructure scan() {
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(Level entityLevel, long time, BlockState blockState, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick(ClientLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick(ServerLevel level, long time, BlockState state, BlockPos pos) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firstTick(Level level, BlockState state, BlockPos pos) {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
@@ -23,7 +24,10 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
||||
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
|
||||
|
||||
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
|
||||
return be.getEnclosedBlocks();
|
||||
// cache = be.getEnclosedBlocks();
|
||||
// if (cache != null)
|
||||
// return cache;
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -31,18 +35,22 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
||||
return AABB.ofSize(blockEntity.getBlockPos().getCenter(), OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2);
|
||||
}
|
||||
|
||||
List<BlockPos> cache;
|
||||
|
||||
private Set<BlockPos> relativePositionsCache;
|
||||
@Override
|
||||
// If in debug mode, renders a model made from the blocks
|
||||
// that are currently returned by toBlockPositions(OxygenTestBlockEntity).
|
||||
public void render(OxygenTestBlockEntity be, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||
if (true) return; // i think this is all deprecated now
|
||||
|
||||
// This bit's debug only, folks!
|
||||
if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return;
|
||||
|
||||
// Renderers are relative to our block pos, so transform all points to be relative to block pos as well
|
||||
List<BlockPos> positionsToRender = toBlockPositions(be);
|
||||
BlockPos originPos = be.getBlockPos();
|
||||
if (true) return;
|
||||
// if (true) return;
|
||||
|
||||
Set<BlockPos> relativePositions;
|
||||
if (relativePositionsCache != null) relativePositions = relativePositionsCache;
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.xevianlight.aphelion.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
@@ -11,7 +13,8 @@ import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
|
||||
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
|
||||
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
|
||||
@@ -50,5 +53,13 @@ public class AphelionDebugOverlay {
|
||||
event.getLeft().add(" Orbit: " + orbitId);
|
||||
// event.getLeft().add(" Sky: " + rendererSummary);
|
||||
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
|
||||
event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination());
|
||||
var server = mc.getSingleplayerServer();
|
||||
ServerLevel singlePlayerLevel;
|
||||
if (server != null) {
|
||||
singlePlayerLevel = server.getLevel(mc.level.dimension());
|
||||
if (singlePlayerLevel != null)
|
||||
event.getLeft().add(" Oxygen: " + EnvironmentSavedData.get(singlePlayerLevel).hasOxygen(singlePlayerLevel, mc.player.blockPosition().mutable().above()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
public final class ClientOxygenCache {
|
||||
public static final LongOpenHashSet OXYGEN = new LongOpenHashSet();
|
||||
public static BlockPos lastCenter = BlockPos.ZERO;
|
||||
public static long lastUpdateGameTime = -1;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public class DebugRenderUtils {
|
||||
|
||||
/// Utilities for dealing with longs instead of BlockPos
|
||||
/// I am not actually sure if this is faster or not. it probably is
|
||||
/// 99% sure that this breaks in edge cases
|
||||
public static class LongPos {
|
||||
private static final int PACKED_X_LENGTH = 26;
|
||||
private static final int PACKED_Y_LENGTH = 12;
|
||||
private static final int PACKED_Z_LENGTH = 26;
|
||||
private static final long PACKED_X_MASK = 67108863L;
|
||||
private static final long PACKED_Y_MASK = 4095L;
|
||||
private static final long PACKED_Z_MASK = 67108863L;
|
||||
private static final int Y_OFFSET = 0;
|
||||
private static final int Z_OFFSET = 12;
|
||||
private static final int X_OFFSET = 38;
|
||||
private static final long Y_MAGIC = PACKED_Y_MASK << Y_OFFSET;
|
||||
private static final long Z_MAGIC = PACKED_Z_MASK << Z_OFFSET;
|
||||
private static final long X_MAGIC = PACKED_X_MASK << X_OFFSET;
|
||||
|
||||
// no safeguard for the maximum values, but those are
|
||||
public static long above(long pos) {
|
||||
long y = (((pos & Y_MAGIC) >> Y_OFFSET) + 1) << Y_OFFSET;
|
||||
return (pos & (~Y_MAGIC)) | (y & Y_MAGIC);
|
||||
}
|
||||
|
||||
public static long below(long pos) {
|
||||
long y = (((pos & Y_MAGIC) >> Y_OFFSET) - 1) << Y_OFFSET;
|
||||
return (pos & (~Y_MAGIC)) | (y & Y_MAGIC);
|
||||
}
|
||||
|
||||
public static long north(long pos) {
|
||||
long z = (((pos & Z_MAGIC) >> Z_OFFSET) - 1) << Z_OFFSET;
|
||||
return (pos & (~Z_MAGIC)) | (z & Z_MAGIC);
|
||||
}
|
||||
|
||||
public static long south(long pos) {
|
||||
long z = (((pos & Z_MAGIC) >> Z_OFFSET) + 1) << Z_OFFSET;
|
||||
return (pos & (~Z_MAGIC)) | (z & Z_MAGIC);
|
||||
}
|
||||
|
||||
public static long east(long pos) {
|
||||
long x = (((pos & X_MAGIC) >> X_OFFSET) + 1) << X_OFFSET;
|
||||
return (pos & (~X_MAGIC)) | (x & X_MAGIC);
|
||||
}
|
||||
|
||||
public static long west(long pos) {
|
||||
long x = (((pos & X_MAGIC) >> X_OFFSET) - 1) << X_OFFSET;
|
||||
return (pos & (~X_MAGIC)) | (x & X_MAGIC);
|
||||
}
|
||||
|
||||
public static int getX(long pos) {
|
||||
return BlockPos.getX(pos);
|
||||
}
|
||||
|
||||
public static int getY(long pos) {
|
||||
return BlockPos.getY(pos);
|
||||
}
|
||||
|
||||
public static int getZ(long pos) {
|
||||
return BlockPos.getZ(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawSphere(PoseStack poseStack, VertexConsumer vc, Vector3f center, float radius) {
|
||||
|
||||
Matrix4f mat = poseStack.last().pose();
|
||||
|
||||
final int Y_SEGMENT_COUNT = 20;
|
||||
for (int segmentY=0; segmentY < Y_SEGMENT_COUNT; segmentY++) {
|
||||
double bottomAngle = (((double) segmentY / Y_SEGMENT_COUNT) - 0.5) * Math.PI;
|
||||
double topAngle = (((double) (segmentY+1) / Y_SEGMENT_COUNT) - 0.5) * Math.PI;
|
||||
float y0 = (float) Math.sin(bottomAngle) * radius + center.y();
|
||||
float y1 = (float) Math.sin(topAngle) * radius + center.y();
|
||||
float bottomRadius = (float) Math.cos(bottomAngle) * radius;
|
||||
float topRadius = (float) Math.cos(topAngle) * radius;
|
||||
|
||||
final int POLAR_SEGMENT_COUNT = 20;
|
||||
for (int segmentP = 0; segmentP < POLAR_SEGMENT_COUNT; segmentP++) {
|
||||
// "left" and "right" As viewed from outside the sphere
|
||||
double leftAngle = (((double) segmentP / POLAR_SEGMENT_COUNT) - 1) * 2 * Math.PI;
|
||||
double rightAngle = (((double) (segmentP+1) / POLAR_SEGMENT_COUNT) - 1) * 2 * Math.PI;
|
||||
// Points have to wind CCW, so 0->1->2->3 is CCW.
|
||||
// 0 and 1 use y0, 2 and 3 use y1.
|
||||
float x0, x1, x2, x3, z0, z1, z2, z3;
|
||||
|
||||
x0 = (float) Math.cos(leftAngle) * bottomRadius + center.x();
|
||||
x1 = (float) Math.cos(rightAngle) * bottomRadius + center.x();
|
||||
x2 = (float) Math.cos(rightAngle) * topRadius + center.x();
|
||||
x3 = (float) Math.cos(leftAngle) * topRadius + center.x();
|
||||
z0 = (float) Math.sin(leftAngle) * bottomRadius + center.z();
|
||||
z1 = (float) Math.sin(rightAngle) * bottomRadius + center.z();
|
||||
z2 = (float) Math.sin(rightAngle) * topRadius + center.z();
|
||||
z3 = (float) Math.sin(leftAngle) * topRadius + center.z();
|
||||
|
||||
// Draw the sphere quad
|
||||
quad(mat, vc,
|
||||
x0, y0, z0,
|
||||
x1, y0, z1,
|
||||
x2, y1, z2,
|
||||
x3, y1, z3,
|
||||
0.7f, 0.2f, 0.2f, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorry LOSERS, we only let FAST data structures in HERE.
|
||||
public static void drawBlockArea(PoseStack poseStack, VertexConsumer vc, LongOpenHashSet blocks) {
|
||||
|
||||
for (long p : blocks) {
|
||||
|
||||
|
||||
// Neighbor checks: only render faces exposed to non-oxygen
|
||||
boolean up = blocks.contains(LongPos.above(p));
|
||||
boolean down = blocks.contains(LongPos.below(p));
|
||||
boolean north = blocks.contains(LongPos.north(p));
|
||||
boolean south = blocks.contains(LongPos.south(p));
|
||||
boolean east = blocks.contains(LongPos.east(p));
|
||||
boolean west = blocks.contains(LongPos.west(p));
|
||||
|
||||
if (up && down && north && south && east && west) continue;
|
||||
|
||||
drawBlockFaces(poseStack, vc, p, up, down, north, south, east, west);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawBlockFaces(
|
||||
PoseStack poseStack, VertexConsumer vc, long posAsLong,
|
||||
boolean up, boolean down, boolean north, boolean south, boolean east, boolean west) {
|
||||
final float eps = 0.0025f;
|
||||
float x0 = LongPos.getX(posAsLong) + eps;
|
||||
float y0 = LongPos.getY(posAsLong) + eps;
|
||||
float z0 = LongPos.getZ(posAsLong) + eps;
|
||||
float x1 = LongPos.getX(posAsLong) + 1 - eps;
|
||||
float y1 = LongPos.getY(posAsLong) + 1 - eps;
|
||||
float z1 = LongPos.getZ(posAsLong) + 1 - eps;
|
||||
|
||||
// Color (ARGB-ish but as floats)
|
||||
float r = 0.2f, g = 0.8f, b = 1.0f, a = 0.18f;
|
||||
|
||||
Matrix4f mat = poseStack.last().pose();
|
||||
|
||||
// IMPORTANT: vertex winding should be consistent (counter-clockwise)
|
||||
if (!up) quad(mat, vc, x0,y1,z0, x1,y1,z0, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||
if (!down) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y0,z0, x0,y0,z0, r,g,b,a);
|
||||
|
||||
if (!north) quad(mat, vc, x1,y0,z0, x0,y0,z0, x0,y1,z0, x1,y1,z0, r,g,b,a);
|
||||
if (!south) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||
|
||||
if (!east) quad(mat, vc, x1,y0,z1, x1,y0,z0, x1,y1,z0, x1,y1,z1, r,g,b,a);
|
||||
if (!west) quad(mat, vc, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, r,g,b,a);
|
||||
}
|
||||
|
||||
public static void quad(
|
||||
Matrix4f mat, VertexConsumer vc,
|
||||
float x0, float y0, float z0,
|
||||
float x1, float y1, float z1,
|
||||
float x2, float y2, float z2,
|
||||
float x3, float y3, float z3,
|
||||
float r, float g, float b, float a
|
||||
) {
|
||||
// POSITION_COLOR format: ONLY position + color.
|
||||
vc.addVertex(mat, x0, y0, z0).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x1, y1, z1).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x2, y2, z2).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x3, y3, z3).setColor(r, g, b, a);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.server.IntegratedServer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.GravitySavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.GravityData;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
|
||||
public class GravityDebugRender {
|
||||
// Untextured translucent quads (POSITION_COLOR only)
|
||||
private static final RenderType GRAVITY_FILL = RenderType.create(
|
||||
"aphelion_gravity_fill",
|
||||
DefaultVertexFormat.POSITION_COLOR,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RenderStateShard.POSITION_COLOR_SHADER)
|
||||
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(RenderStateShard.NO_CULL)
|
||||
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
|
||||
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
|
||||
.createCompositeState(true)
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevel(RenderLevelStageEvent event) {
|
||||
// One stage only (pick one that exists and looks good)
|
||||
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
if (!mc.gui.getDebugOverlay().showDebugScreen()) return;
|
||||
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
var cam = mc.gameRenderer.getMainCamera();
|
||||
var camPos = cam.getPosition();
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(-camPos.x, -camPos.y, -camPos.z);
|
||||
|
||||
// I'm lazy, so i'm just gonna make this work on a singleplayer server and call it a day :P
|
||||
IntegratedServer singleplayer = mc.getSingleplayerServer();
|
||||
if (singleplayer == null) return;
|
||||
|
||||
MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource();
|
||||
VertexConsumer vc = bufferSource.getBuffer(GRAVITY_FILL);
|
||||
|
||||
for (Long2IntMap.Entry gravityEntry : GravitySavedData.get(singleplayer.getLevel(mc.level.dimension()))._debug_getGravityData().long2IntEntrySet()) {
|
||||
GravityData d = GravityData.unpack(gravityEntry.getIntValue());
|
||||
BlockPos p = BlockPos.of(gravityEntry.getLongKey());
|
||||
|
||||
DebugRenderUtils.drawSphere(poseStack, vc, p.getCenter().toVector3f(), d.getRadius());
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
bufferSource.endBatch(GRAVITY_FILL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
|
||||
public final class OxygenDebugRender {
|
||||
|
||||
// Untextured translucent quads (POSITION_COLOR only)
|
||||
private static final RenderType OXYGEN_FILL = RenderType.create(
|
||||
"aphelion_oxygen_fill",
|
||||
DefaultVertexFormat.POSITION_COLOR,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RenderStateShard.POSITION_COLOR_SHADER)
|
||||
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(RenderStateShard.NO_CULL)
|
||||
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
|
||||
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
|
||||
.createCompositeState(true)
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevel(RenderLevelStageEvent event) {
|
||||
// One stage only (pick one that exists and looks good)
|
||||
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
if (!mc.gui.getDebugOverlay().showDebugScreen()) return;
|
||||
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
var cam = mc.gameRenderer.getMainCamera();
|
||||
var camPos = cam.getPosition();
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(-camPos.x, -camPos.y, -camPos.z);
|
||||
|
||||
MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource();
|
||||
VertexConsumer vc = bufferSource.getBuffer(OXYGEN_FILL);
|
||||
|
||||
DebugRenderUtils.drawBlockArea(poseStack, vc, ClientOxygenCache.OXYGEN);
|
||||
|
||||
poseStack.popPose();
|
||||
bufferSource.endBatch(OXYGEN_FILL);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import net.xevianlight.aphelion.network.packet.PartitionData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PartitionClientState {
|
||||
private static volatile PartitionData last = null;
|
||||
private static volatile PartitionPayload last = null;
|
||||
|
||||
public static void set(PartitionData d) { last = d; }
|
||||
public static void set(PartitionPayload d) { last = d; }
|
||||
|
||||
public static Optional<PartitionData> get() {
|
||||
public static Optional<PartitionPayload> get() {
|
||||
return Optional.ofNullable(last);
|
||||
}
|
||||
|
||||
public static String idOrUnknown() {
|
||||
return last != null ? last.id() : "unknown";
|
||||
String orbit = String.valueOf(last.partitionData().getOrbit());
|
||||
if (orbit == null) {
|
||||
return "aphleion:orbit/default";
|
||||
}
|
||||
return last != null ? orbit : "unknown";
|
||||
}
|
||||
|
||||
public static PartitionData lastData() {
|
||||
return last.partitionData();
|
||||
}
|
||||
//
|
||||
// public static int pxOr(int fallback) {
|
||||
|
||||
@@ -71,7 +71,7 @@ public class DimensionSkyEffects extends DimensionSpecialEffects {
|
||||
// int py = PartitionClientState.pyOr(0);
|
||||
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
// var data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
if (data != null) return data;
|
||||
|
||||
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
|
||||
@@ -5,12 +5,9 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.DimensionSpecialEffects;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.PartitionClientState;
|
||||
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -29,7 +26,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
|
||||
return ResourceLocation.withDefaultNamespace("overworld");
|
||||
}
|
||||
if (effectsId.equals(Aphelion.id("space"))) {
|
||||
return SpaceSkyEffects.orbitForPos(camera.getPosition()); // or inline this logic
|
||||
return orbitForPos(camera.getPosition()); // or inline this logic
|
||||
}
|
||||
return effectsId;
|
||||
}
|
||||
@@ -80,7 +77,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
|
||||
// int py = PartitionClientState.pyOr(0);
|
||||
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown());
|
||||
|
||||
// var data = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
// var partitionData = SpacePartitionSavedData.get(serverLevel).getOrbitForPartition((int) x, (int) z);
|
||||
if (data != null) return data;
|
||||
|
||||
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");
|
||||
|
||||
@@ -15,17 +15,18 @@ import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ColumnPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.RelativeMovement;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.util.RocketStructure;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@@ -244,6 +245,24 @@ public class AphelionCommand {
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("destination")
|
||||
.then(Commands.literal("set").then(
|
||||
Commands.argument("pos", ColumnPosArgument.columnPos())
|
||||
.then(Commands.argument("id", ResourceLocationArgument.id())
|
||||
.executes(context -> {
|
||||
int x = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").x());
|
||||
int z = SpacePartitionHelper.get(ColumnPosArgument.getColumnPos(context, "pos").z());
|
||||
ResourceLocation orbit = ResourceLocationArgument.getId(context, "id");
|
||||
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
SpacePartitionSavedData.get(level).getData(x,z).setDestination(orbit);
|
||||
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("planet")
|
||||
.then(Commands.literal("tp")
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package net.xevianlight.aphelion.core;
|
||||
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KeyVariables {
|
||||
|
||||
public static final Map<UUID, Boolean> KEY_UP = new HashMap<>();
|
||||
public static final Map<UUID, Boolean> KEY_DOWN = new HashMap<>();
|
||||
public static final Map<UUID, Boolean> KEY_RIGHT = new HashMap<>();
|
||||
public static final Map<UUID, Boolean> KEY_LEFT = new HashMap<>();
|
||||
public static final Map<UUID, Boolean> KEY_JUMP = new HashMap<>();
|
||||
public static final Map<UUID, Boolean> KEY_TABLET = new HashMap<>();
|
||||
|
||||
public static boolean isHoldingUp(Player player) {
|
||||
return player != null && KEY_UP.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
public static boolean isHoldingDown(Player player) {
|
||||
return player != null && KEY_DOWN.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
public static boolean isHoldingRight(Player player) {
|
||||
return player != null && KEY_RIGHT.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
public static boolean isHoldingLeft(Player player) {
|
||||
return player != null && KEY_LEFT.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
public static boolean isHoldingJump(Player player) {
|
||||
return player != null && KEY_JUMP.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
public static boolean getHoldingTabletPress(Player player) {
|
||||
return player != null && KEY_TABLET.getOrDefault(player.getUUID(), false);
|
||||
}
|
||||
|
||||
|
||||
public static Map<KeyMapping, String> getKey(Minecraft minecraft) {
|
||||
Map<KeyMapping, String> key = new HashMap<>();
|
||||
key.put(minecraft.options.keyUp, "key_up");
|
||||
key.put(minecraft.options.keyDown, "key_down");
|
||||
key.put(minecraft.options.keyRight, "key_right");
|
||||
key.put(minecraft.options.keyLeft, "key_left");
|
||||
key.put(minecraft.options.keyJump, "key_jump");
|
||||
return key;
|
||||
}
|
||||
|
||||
public static void setKeyVariable(String key, UUID uuid, Boolean bool) {
|
||||
switch (key) {
|
||||
case "key_up":
|
||||
KEY_UP.put(uuid, bool);
|
||||
case "key_down":
|
||||
KEY_DOWN.put(uuid, bool);
|
||||
case "key_right":
|
||||
KEY_RIGHT.put(uuid, bool);
|
||||
case "key_left":
|
||||
KEY_LEFT.put(uuid, bool);
|
||||
case "key_jump":
|
||||
KEY_JUMP.put(uuid, bool);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,4 +46,14 @@ public class ModBlockEntities {
|
||||
BLOCK_ENTITIES.register("oxygen_test_block_entity", () -> BlockEntityType.Builder.of(
|
||||
OxygenTestBlockEntity::new, ModBlocks.OXYGEN_TEST_BLOCK.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<RocketAssemblerBlockEntity>> ROCKET_ASSEMBLER_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("rocket_assembler_block_entity", () -> BlockEntityType.Builder.of(
|
||||
RocketAssemblerBlockEntity::new, ModBlocks.ROCKET_ASSEMBLER_BLOCK.get()).build(null)
|
||||
);
|
||||
|
||||
public static final Supplier<BlockEntityType<GravityTestBlockEntity>> GRAVITY_TEST_BLOCK_ENTITY =
|
||||
BLOCK_ENTITIES.register("gravity_test_block_entity", () -> BlockEntityType.Builder.of(
|
||||
GravityTestBlockEntity::new, ModBlocks.GRAVITY_TEST_BLOCK.get()).build(null)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,16 +6,20 @@ import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.custom.*;
|
||||
import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock;
|
||||
import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity;
|
||||
|
||||
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> OXYGEN_TEST_BLOCK = BLOCKS.register("oxygen_test_block", () -> new OxygenTestBlock(OxygenTestBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> ROCKET_ASSEMBLER_BLOCK = BLOCKS.register("rocket_assemblerblock", () -> new RocketAssemblerBlock(RocketAssemblerBlock.getProperties()));
|
||||
public static final DeferredBlock<Block> GRAVITY_TEST_BLOCK = BLOCKS.register("gravity_test_block", () -> new GravityTestBlock(GravityTestBlock.getProperties()));
|
||||
}
|
||||
|
||||
@@ -41,5 +41,6 @@ public class ModCreativeTabs {
|
||||
output.accept(ModItems.BLOCK_STEEL);
|
||||
output.accept(ModItems.ARC_FURNACE_CASING_BLOCK);
|
||||
output.accept(ModItems.VACUUM_ARC_FURNACE_CONTROLLER);
|
||||
output.accept(ModItems.LAUNCH_PAD);
|
||||
}).build());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.xevianlight.aphelion.core.init;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.damagesource.DamageType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
public class ModDamageSources {
|
||||
|
||||
// Relatively sure this is right
|
||||
public static final ResourceKey<DamageType> OXYGEN = ResourceKey.create(Registries.DAMAGE_TYPE, ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "oxygen"));
|
||||
|
||||
public static DamageSource create(Level level, ResourceKey<DamageType> key) {
|
||||
return new DamageSource(level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(key));
|
||||
}
|
||||
}
|
||||
@@ -36,5 +36,7 @@ public static final DeferredItem<Item> MUSIC_DISC_BIT_SHIFT = ITEMS.register("mu
|
||||
public static final DeferredItem<BlockItem> ARC_FURNACE_CASING_BLOCK = ITEMS.register("arc_furnace_casing", () -> new BlockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK.get(), ArcFurnaceCasingBlock.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> VACUUM_ARC_FURNACE_CONTROLLER = ITEMS.register("vacuum_arc_furnace_controller", () -> new BlockItem(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get(), VacuumArcFurnaceController.getItemProperties()));
|
||||
public static final DeferredItem<BlockItem> OXYGEN_TEST_BLOCK = ITEMS.register("oxygen_test_block", () -> new BlockItem(ModBlocks.OXYGEN_TEST_BLOCK.get(), new Item.Properties()));
|
||||
public static final DeferredItem<BlockItem> GRAVITY_TEST_BLOCK = ITEMS.register("gravity_test_block", () -> new BlockItem(ModBlocks.GRAVITY_TEST_BLOCK.get(), new Item.Properties()));
|
||||
public static final DeferredItem<BlockItem> LAUNCH_PAD = ITEMS.register("launch_pad", () -> new BlockItem(ModBlocks.LAUNCH_PAD.get(), LaunchPad.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()));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public class ModSounds {
|
||||
public static final Supplier<SoundEvent> BIT_SHIFT = registerSoundEvent("bit_shift");
|
||||
public static final ResourceKey<JukeboxSong> BIT_SHIFT_KEY = createSong("bit_shift");
|
||||
|
||||
public static final Supplier<SoundEvent> ROCKET_ENGINE = registerSoundEvent("rocket_engine");
|
||||
|
||||
private static Supplier<SoundEvent> registerSoundEvent(String name) {
|
||||
ResourceLocation id = ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, name);
|
||||
return SOUND_EVENTS.register(name, () -> SoundEvent.createVariableRangeEvent(id));
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package net.xevianlight.aphelion.core.saveddata;
|
||||
|
||||
import com.jcraft.jorbis.Block;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.ClientOxygenCache;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Pattern:
|
||||
* - World-level SavedData
|
||||
* - Outer map keyed by section (chunkX, sectionY, chunkZ) packed into a long
|
||||
* - Inner map keyed by localIndex (0..4095) -> packed int env value
|
||||
*
|
||||
* Sparse by design: blocks not present in the inner map are implicitly "default environment".
|
||||
*/
|
||||
public class EnvironmentSavedData extends SavedData {
|
||||
|
||||
private final Long2IntOpenHashMap envData = new Long2IntOpenHashMap();
|
||||
|
||||
private static final String NAME = "aphelion_environment";
|
||||
|
||||
public static EnvironmentSavedData create() {
|
||||
return new EnvironmentSavedData();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CompoundTag save(@NotNull CompoundTag tag, @NotNull HolderLookup.Provider provider) {
|
||||
int size = envData.size();
|
||||
long[] positions = new long[size];
|
||||
int[] data = new int[size];
|
||||
|
||||
int i = 0;
|
||||
for (var e : envData.long2IntEntrySet()) {
|
||||
positions[i] = e.getLongKey();
|
||||
data[i] = e.getIntValue();
|
||||
i++;
|
||||
}
|
||||
|
||||
tag.putLongArray("Position", positions);
|
||||
tag.putIntArray("Value", data);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static EnvironmentSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) {
|
||||
EnvironmentSavedData data = create();
|
||||
|
||||
if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; }
|
||||
|
||||
long[] positions = tag.getLongArray("Position");
|
||||
int[] values = tag.getIntArray("Value");
|
||||
|
||||
int length = Math.min(positions.length, values.length);
|
||||
|
||||
data.envData.ensureCapacity(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.envData.put(positions[i], values[i]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public EnvironmentData getDataForPosition(Level level, BlockPos pos) {
|
||||
Planet planet = PlanetCache.getByDimensionOrNull(level.dimension());
|
||||
int packedDefault;
|
||||
|
||||
if (planet == null) {
|
||||
packedDefault = EnvironmentData.DEFAULT_PACKED;
|
||||
} else {
|
||||
EnvironmentData planetData = new EnvironmentData(planet.oxygen(), EnvironmentData.DEFAULT_TEMPERATURE, (short) planet.gravity());
|
||||
packedDefault = planetData.pack();
|
||||
}
|
||||
int packed = envData.getOrDefault(pos.asLong(),packedDefault);
|
||||
return EnvironmentData.unpack(packed);
|
||||
}
|
||||
|
||||
public void setDataForPosition(Level level, BlockPos pos, EnvironmentData data) {
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public boolean hasOxygen(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
return data.hasOxygen();
|
||||
}
|
||||
|
||||
public void setOxygen(Level level, BlockPos pos, boolean value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setOxygen(value);
|
||||
// Aphelion.LOGGER.info("Set oxygen for {} to {}", pos, value);
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setOxygen(Level level, Collection<BlockPos> positions, boolean value) {
|
||||
for (BlockPos pos : positions) {
|
||||
setOxygen(level, pos, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetOxygen(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setOxygen(defaultData(level).hasOxygen());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void resetOxygen(Level level, Collection<BlockPos> positions) {
|
||||
for (BlockPos pos : positions) {
|
||||
resetOxygen(level, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public float getGravity(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
return data.getGravity();
|
||||
}
|
||||
|
||||
public void setGravity(Level level, BlockPos pos, float value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setGravity(value);
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setGravity(Level level, Collection<BlockPos> positions, float value) {
|
||||
for (BlockPos pos : positions) {
|
||||
setGravity(level, pos, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetGravity(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setGravity(defaultData(level).getGravity());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public short getTemperature(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
return data.getTemperature();
|
||||
}
|
||||
|
||||
public void setTemperature(Level level, BlockPos pos, short value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setTemperature(value);
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setTemperature(Level level, Collection<BlockPos> positions, short value) {
|
||||
for (BlockPos pos : positions) {
|
||||
setTemperature(level, pos, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTemperature(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setTemperature(defaultData(level).getTemperature());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
private void putOrRemove(Level level, long key, int packed) {
|
||||
if (packed == defaultPacked(level)) {
|
||||
envData.remove(key);
|
||||
} else {
|
||||
envData.put(key, packed);
|
||||
}
|
||||
setDirty();
|
||||
}
|
||||
|
||||
private static int defaultPacked(Level level) {
|
||||
Planet planet = PlanetCache.getByDimensionOrNull(level.dimension());
|
||||
if (planet == null) return EnvironmentData.DEFAULT_PACKED;
|
||||
|
||||
// NOTE: adjust gravity/temperature defaults to whatever your data model intends
|
||||
EnvironmentData planetData = new EnvironmentData(
|
||||
planet.oxygen(),
|
||||
EnvironmentData.DEFAULT_TEMPERATURE,
|
||||
(short) planet.gravity()
|
||||
);
|
||||
return planetData.pack();
|
||||
}
|
||||
|
||||
private static EnvironmentData defaultData(Level level) {
|
||||
return EnvironmentData.unpack(defaultPacked(level));
|
||||
}
|
||||
|
||||
public static EnvironmentSavedData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(
|
||||
new Factory<>(EnvironmentSavedData::create, EnvironmentSavedData::load),
|
||||
NAME
|
||||
);
|
||||
}
|
||||
|
||||
public static void refreshFromIntegratedServerIfNeeded(Minecraft mc, int radius, int maxBlocks) {
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
|
||||
long gameTime = mc.level.getGameTime();
|
||||
if (ClientOxygenCache.lastUpdateGameTime != -1 && gameTime - ClientOxygenCache.lastUpdateGameTime < 20) return; // every 1s
|
||||
|
||||
BlockPos center = mc.player.blockPosition();
|
||||
if (center.distManhattan(ClientOxygenCache.lastCenter) < 1) return; // don’t refresh if player barely moved
|
||||
|
||||
var server = mc.getSingleplayerServer();
|
||||
if (server == null) return;
|
||||
|
||||
// IMPORTANT: execute on server thread
|
||||
server.execute(() -> {
|
||||
var serverLevel = server.getLevel(mc.level.dimension());
|
||||
if (serverLevel == null) return;
|
||||
|
||||
EnvironmentSavedData env = EnvironmentSavedData.get(serverLevel);
|
||||
|
||||
LongOpenHashSet found = new LongOpenHashSet();
|
||||
|
||||
int r = radius;
|
||||
int scanned = 0;
|
||||
|
||||
// Scan a cube-ish region
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (found.size() >= maxBlocks) break;
|
||||
|
||||
BlockPos p = center.offset(dx, dy, dz);
|
||||
|
||||
// optional: skip non-air or skip solid blocks (visual preference)
|
||||
// if (!serverLevel.getBlockState(p).isAir()) continue;
|
||||
|
||||
if (env.hasOxygen(serverLevel, p)) {
|
||||
found.add(p.asLong());
|
||||
}
|
||||
|
||||
scanned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy results back to client thread safely
|
||||
mc.execute(() -> {
|
||||
ClientOxygenCache.OXYGEN.clear();
|
||||
ClientOxygenCache.OXYGEN.addAll(found);
|
||||
ClientOxygenCache.lastCenter = center;
|
||||
ClientOxygenCache.lastUpdateGameTime = gameTime;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package net.xevianlight.aphelion.core.saveddata;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import net.xevianlight.aphelion.client.ClientOxygenCache;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.GravityData;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pattern:
|
||||
* - World-level SavedData
|
||||
* - Outer map keyed by section (chunkX, sectionY, chunkZ) packed into a long
|
||||
* - Inner map keyed by localIndex (0..4095) -> packed int env value
|
||||
*
|
||||
* Sparse by design: blocks not present in the inner map are implicitly "default environment".
|
||||
*/
|
||||
public class GravitySavedData extends SavedData {
|
||||
|
||||
private Level level;
|
||||
|
||||
private final Long2IntOpenHashMap gravityData = new Long2IntOpenHashMap();
|
||||
|
||||
private static final String NAME = "aphelion_gravity";
|
||||
|
||||
public static GravitySavedData create(Level level) {
|
||||
return new GravitySavedData(level);
|
||||
}
|
||||
|
||||
public GravitySavedData(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CompoundTag save(@NotNull CompoundTag tag, @NotNull HolderLookup.Provider provider) {
|
||||
int size = gravityData.size();
|
||||
long[] positions = new long[size];
|
||||
int[] data = new int[size];
|
||||
|
||||
int i = 0;
|
||||
for (var e : gravityData.long2IntEntrySet()) {
|
||||
positions[i] = e.getLongKey();
|
||||
data[i] = e.getIntValue();
|
||||
i++;
|
||||
}
|
||||
|
||||
tag.putLongArray("Position", positions);
|
||||
tag.putIntArray("Value", data);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static GravitySavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider, Level level) {
|
||||
GravitySavedData data = new GravitySavedData(level);
|
||||
|
||||
if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
long[] positions = tag.getLongArray("Position");
|
||||
int[] values = tag.getIntArray("Value");
|
||||
int length = Math.min(positions.length, values.length);
|
||||
|
||||
data.gravityData.ensureCapacity(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.gravityData.put(positions[i], values[i]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static final int ABSENT = Integer.MIN_VALUE;
|
||||
|
||||
public @Nullable GravityData getGravityRegionData (BlockPos center) {
|
||||
GravityData data = GravityData.unpack(gravityData.getOrDefault(center.asLong(), ABSENT));
|
||||
return data.pack() == ABSENT ? null : data;
|
||||
}
|
||||
|
||||
public void removeGravityRegion (BlockPos pos) {
|
||||
gravityData.remove(pos.asLong());
|
||||
}
|
||||
|
||||
public void setGravityRegion (BlockPos pos, GravityData data) {
|
||||
gravityData.put(pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public Long2IntOpenHashMap _debug_getGravityData() {
|
||||
return gravityData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the cumulative sum of the acceleration for all gravity regions that overlap this block position
|
||||
*/
|
||||
public float getGravitySum (BlockPos pos) {
|
||||
float sum = 0;
|
||||
|
||||
List<GravityData> regions = getGravityRegions(pos);
|
||||
|
||||
for (var e : regions) {
|
||||
sum += e.getAccel();
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the strongest acceleration among all gravity regions that overlap this block position
|
||||
*/
|
||||
public float getGravityMax (BlockPos pos) {
|
||||
float max = -1;
|
||||
|
||||
List<GravityData> regions = getGravityRegions(pos);
|
||||
|
||||
for (var e : regions) {
|
||||
var accel = e.getAccel();
|
||||
if (accel > max) max = accel;
|
||||
}
|
||||
|
||||
if (max == -1) {
|
||||
max = defaultGravity(this.level);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all gravity data objects overlapping this position. NOTE: does not contain position of the regions
|
||||
*/
|
||||
public List<GravityData> getGravityRegions (BlockPos pos) {
|
||||
List<GravityData> regions = new ArrayList<>();
|
||||
for (var entry : gravityData.long2IntEntrySet()) {
|
||||
GravityData data = GravityData.unpack(entry.getIntValue());
|
||||
BlockPos center = BlockPos.of(entry.getLongKey());
|
||||
|
||||
if (contains(pos, center, data.getRadius())) {
|
||||
regions.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
|
||||
private static boolean contains(BlockPos pos, BlockPos center, float radius) {
|
||||
float distanceSquared = ((center.getX() - pos.getX()) * (center.getX() - pos.getX())) + ((center.getY() - pos.getY()) * (center.getY() - pos.getY())) + ((center.getZ() - pos.getZ()) * (center.getZ() - pos.getZ()));
|
||||
return distanceSquared <= radius * radius;
|
||||
}
|
||||
|
||||
private static float defaultGravity (Level level) {
|
||||
Planet planet = PlanetCache.getByDimensionOrNull(level.dimension());
|
||||
if (planet == null) return GravityData.DEFAULT_GRAVITY;
|
||||
return planet.gravity();
|
||||
}
|
||||
|
||||
public static GravitySavedData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(
|
||||
new Factory<>(
|
||||
() -> new GravitySavedData(level),
|
||||
(tag, provider) -> GravitySavedData.load(tag, provider, level)
|
||||
),
|
||||
NAME
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package net.xevianlight.aphelion.core.saveddata;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SpacePartitionSavedData extends SavedData {
|
||||
|
||||
private static final String NAME = "aphelion_station_partitions";
|
||||
|
||||
private final Long2ObjectMap<PartitionData> map = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public static SpacePartitionSavedData create() {
|
||||
return new SpacePartitionSavedData();
|
||||
}
|
||||
|
||||
public static SpacePartitionSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) {
|
||||
SpacePartitionSavedData data = create();
|
||||
|
||||
ListTag entries = tag.getList("Entries", CompoundTag.TAG_COMPOUND);
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
CompoundTag e = entries.getCompound(i);
|
||||
|
||||
long key = e.getLong("Key");
|
||||
|
||||
ResourceLocation orbitRL = null;
|
||||
if (e.contains("Orbit", CompoundTag.TAG_STRING)) {
|
||||
orbitRL = ResourceLocation.tryParse(e.getString("Orbit"));
|
||||
}
|
||||
|
||||
PartitionData pd = new PartitionData(orbitRL);
|
||||
|
||||
// Destination (optional)
|
||||
if (e.contains("Destination", CompoundTag.TAG_STRING)) {
|
||||
ResourceLocation destRL = ResourceLocation.tryParse(e.getString("Destination"));
|
||||
pd.setDestination(destRL); // ok if null (parse fail)
|
||||
} else {
|
||||
pd.setDestination(null);
|
||||
}
|
||||
|
||||
// Traveling (optional; default false)
|
||||
if (e.contains("Traveling", CompoundTag.TAG_BYTE)) {
|
||||
pd.setTraveling(e.getBoolean("Traveling"));
|
||||
}
|
||||
|
||||
// Distances (optional; default 0.0)
|
||||
if (e.contains("DistanceTraveled", CompoundTag.TAG_DOUBLE)) {
|
||||
pd.setDistanceTraveled(e.getDouble("DistanceTraveled"));
|
||||
}
|
||||
if (e.contains("DistanceToDest", CompoundTag.TAG_DOUBLE)) {
|
||||
pd.setDistanceToDest(e.getDouble("DistanceToDest"));
|
||||
}
|
||||
|
||||
data.map.put(key, pd);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompoundTag save(CompoundTag tag, HolderLookup.@NotNull Provider registries) {
|
||||
ListTag entries = new ListTag();
|
||||
|
||||
map.long2ObjectEntrySet().forEach(entry -> {
|
||||
long key = entry.getLongKey();
|
||||
PartitionData pd = entry.getValue();
|
||||
|
||||
CompoundTag e = new CompoundTag();
|
||||
e.putLong("Key", key);
|
||||
|
||||
// Orbit
|
||||
if (pd.getOrbit() != null) {
|
||||
e.putString("Orbit", pd.getOrbit().toString());
|
||||
}
|
||||
|
||||
// Destination (only if present)
|
||||
if (pd.getDestination() != null) {
|
||||
e.putString("Destination", pd.getDestination().toString());
|
||||
}
|
||||
|
||||
// Traveling + distances
|
||||
e.putBoolean("Traveling", pd.isTraveling());
|
||||
|
||||
e.putDouble("DistanceTraveled", pd.getDistanceTraveled());
|
||||
e.putDouble("DistanceToDest", pd.getDistanceToDest());
|
||||
|
||||
entries.add(e);
|
||||
});
|
||||
|
||||
tag.put("Entries", entries);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
public @Nullable ResourceLocation getOrbitForPartition(int px, int pz) {
|
||||
PartitionData data = map.get(pack(px, pz));
|
||||
if (data == null) return null;
|
||||
return map.get(pack(px, pz)).getOrbit();
|
||||
}
|
||||
|
||||
public void setOrbitForPartition(int px, int pz, ResourceLocation orbit) {
|
||||
long key = pack(px, pz);
|
||||
PartitionData prev = map.get(key);
|
||||
PartitionData newData = new PartitionData(prev);
|
||||
newData.setOrbit(orbit);
|
||||
if (!newData.equals(prev)) {
|
||||
map.put(key, newData);
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clearOrbitForPartition(int px, int pz) {
|
||||
long key = pack(px, pz);
|
||||
PartitionData removed = map.remove(key);
|
||||
if (removed != null) {
|
||||
setDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void clearAllOrbits() {
|
||||
if (!map.isEmpty()) {
|
||||
map.clear();
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable PartitionData getData(int px, int pz) {
|
||||
long key = pack(px, pz);
|
||||
PartitionData data = map.get(key);
|
||||
if (data == null) {
|
||||
// pick a sensible default orbit, or null if you truly allow it
|
||||
data = new PartitionData(Aphelion.id("orbit/default"));
|
||||
map.put(key, data);
|
||||
setDirty();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void overwriteAllExistingOrbits(ResourceLocation orbit) {
|
||||
if (map.isEmpty()) return;
|
||||
|
||||
boolean changed = false;
|
||||
for (var entry : map.long2ObjectEntrySet()) {
|
||||
if(!orbit.equals(entry.getValue())) {
|
||||
entry.getValue().setOrbit(orbit);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) setDirty();
|
||||
}
|
||||
|
||||
public static SpacePartitionSavedData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(
|
||||
new Factory<>(SpacePartitionSavedData::create, SpacePartitionSavedData::load),
|
||||
NAME
|
||||
);
|
||||
}
|
||||
|
||||
public static long pack(int px, int pz) {
|
||||
return (((long) px) << 32) | (pz & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static int unpackX(long key) {
|
||||
return (int)(key >> 32);
|
||||
}
|
||||
|
||||
public static int unpackZ(long key) {
|
||||
return (int)key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package net.xevianlight.aphelion.core.saveddata.types;
|
||||
|
||||
public final class EnvironmentData {
|
||||
|
||||
|
||||
|
||||
public static final boolean DEFAULT_OXYGEN = true;
|
||||
public static final short DEFAULT_TEMPERATURE = (short) 294.2611; // 70F
|
||||
public static final float DEFAULT_GRAVITY = 9.80665f; // 1G
|
||||
|
||||
public static final int DEFAULT_PACKED = new EnvironmentData(DEFAULT_OXYGEN, DEFAULT_TEMPERATURE, DEFAULT_GRAVITY).pack();
|
||||
|
||||
/* We can pack all of this into an int value per block position.
|
||||
* If we have to store partitionData for an entire chunk section (16^3), this amounts to 16kB per section.
|
||||
* 1000 sections touched is 16MB
|
||||
* This is acceptable for partitionData that will only exist where it is not equal to the default values
|
||||
*/
|
||||
|
||||
private static final int OXYGEN_BITS = 1; // Boolean. Do we have oxygen or no?
|
||||
private static final int TEMPERATURE_BITS = Short.SIZE; // 16 bits should suffice for temperature, gives 0k to 65536k range, more than enough
|
||||
private static final int GRAVITY_BITS = 15; // Leftover bits can be assigned to gravity, 32768 values
|
||||
|
||||
private static final float GRAVITY_PRECISION = 100.0f; // 2 decimal precision
|
||||
|
||||
private static final int OXYGEN_BIT = 0;
|
||||
private static final int TEMPERATURE_BIT = OXYGEN_BIT + OXYGEN_BITS; // next 16 bits
|
||||
private static final int GRAVITY_BIT = TEMPERATURE_BIT + TEMPERATURE_BITS; // next 15 bits
|
||||
|
||||
private boolean oxygen;
|
||||
private short temperature;
|
||||
private float gravity;
|
||||
|
||||
public EnvironmentData(boolean oxygen, short temperature, float gravity) {
|
||||
this.oxygen = oxygen;
|
||||
this.temperature = temperature;
|
||||
this.gravity = gravity;
|
||||
}
|
||||
|
||||
public int pack() {
|
||||
int packedData = 0;
|
||||
|
||||
packedData |= (this.oxygen ? 1 : 0) << OXYGEN_BIT;
|
||||
packedData |= (this.temperature & ((1 << TEMPERATURE_BITS) - 1)) << TEMPERATURE_BIT;
|
||||
packedData |= (int) (this.gravity * GRAVITY_PRECISION) << GRAVITY_BIT;
|
||||
|
||||
return packedData;
|
||||
}
|
||||
|
||||
public static EnvironmentData unpack(int packedData) {
|
||||
boolean oxygen = ((packedData >> OXYGEN_BIT) & 1) == 1;
|
||||
short temperature = (short) ((packedData >> TEMPERATURE_BIT) & ((1 << TEMPERATURE_BITS) - 1));
|
||||
float gravity = ((packedData >> GRAVITY_BIT) & ((1 << GRAVITY_BITS) - 1)) / GRAVITY_PRECISION;
|
||||
|
||||
return new EnvironmentData(oxygen, temperature, gravity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (EnvironmentData) obj;
|
||||
return this.oxygen == that.oxygen &&
|
||||
this.temperature == that.temperature &&
|
||||
Float.floatToIntBits(this.gravity) == Float.floatToIntBits(that.gravity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EnvironmentData[" +
|
||||
"oxygen=" + oxygen + ", " +
|
||||
"temperature=" + temperature + ", " +
|
||||
"gravity=" + gravity + ']';
|
||||
}
|
||||
|
||||
public boolean hasOxygen() {
|
||||
return oxygen;
|
||||
}
|
||||
|
||||
public void setOxygen(boolean oxygen) {
|
||||
this.oxygen = oxygen;
|
||||
}
|
||||
|
||||
public short getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(short temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public float getGravity() {
|
||||
return gravity;
|
||||
}
|
||||
|
||||
public void setGravity(float gravity) {
|
||||
this.gravity = gravity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.xevianlight.aphelion.core.saveddata.types;
|
||||
|
||||
public class GravityData {
|
||||
private float accel;
|
||||
private float radius;
|
||||
|
||||
public static final float ONE_G = 9.80665f;
|
||||
public static final float DEFAULT_GRAVITY = ONE_G; // 1G
|
||||
public static final float GRAVITY_PRECISION = 100.0f;
|
||||
public static final float RADIUS_PRECISION = 100.0f;
|
||||
public static final float MAX_RADIUS = Short.MAX_VALUE / RADIUS_PRECISION;
|
||||
public static final float MAX_GRAVITY = Short.MAX_VALUE / GRAVITY_PRECISION;
|
||||
|
||||
public GravityData(float accel, float radius) {
|
||||
this.accel = accel;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public int pack() {
|
||||
int packed = 0;
|
||||
|
||||
packed |= (int) (this.accel * GRAVITY_PRECISION);
|
||||
packed |= (int) (this.radius * RADIUS_PRECISION) << 16;
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
public float getAccel() {
|
||||
return accel;
|
||||
}
|
||||
|
||||
public void setAccel(float accel) {
|
||||
this.accel = accel;
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
public void setRadius(short radius) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public static GravityData unpack(int packed) {
|
||||
float accel = (packed & 0xFFFF) / GRAVITY_PRECISION;
|
||||
float radius = (packed >> 16) / RADIUS_PRECISION;
|
||||
|
||||
return new GravityData(accel, radius);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package net.xevianlight.aphelion.core.saveddata.types;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PartitionData {
|
||||
@Nullable private ResourceLocation orbit;
|
||||
@Nullable private ResourceLocation destination;
|
||||
private boolean traveling;
|
||||
private double distanceTraveled;
|
||||
private double distanceToDest;
|
||||
|
||||
public PartitionData(@Nullable ResourceLocation orbit) {
|
||||
this.orbit = orbit;
|
||||
this.destination = null;
|
||||
this.traveling = false;
|
||||
this.distanceTraveled = 0;
|
||||
this.distanceToDest = 0;
|
||||
}
|
||||
|
||||
public PartitionData(PartitionData other) {
|
||||
this.orbit = other.orbit;
|
||||
this.destination = other.destination;
|
||||
this.traveling = other.traveling;
|
||||
this.distanceTraveled = other.distanceTraveled;
|
||||
this.distanceToDest = other.distanceToDest;
|
||||
}
|
||||
|
||||
public static final StreamCodec<ByteBuf, PartitionData> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
// orbit is nullable -> optional codec
|
||||
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
|
||||
d -> Optional.ofNullable(d.getOrbit()),
|
||||
|
||||
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
|
||||
d -> Optional.ofNullable(d.getDestination()),
|
||||
|
||||
ByteBufCodecs.BOOL,
|
||||
PartitionData::isTraveling,
|
||||
|
||||
// doubles -> DOUBLE codec
|
||||
ByteBufCodecs.DOUBLE,
|
||||
PartitionData::getDistanceTraveled,
|
||||
|
||||
ByteBufCodecs.DOUBLE,
|
||||
PartitionData::getDistanceToDest,
|
||||
|
||||
(orbitOpt, destOpt, traveling, distTraveled, distToDest) -> {
|
||||
PartitionData data = new PartitionData(orbitOpt.orElse(null));
|
||||
data.destination = destOpt.orElse(null);
|
||||
data.traveling = traveling;
|
||||
data.distanceTraveled = distTraveled;
|
||||
data.distanceToDest = distToDest;
|
||||
return data;
|
||||
}
|
||||
);
|
||||
|
||||
public @Nullable ResourceLocation getOrbit() {
|
||||
return this.orbit;
|
||||
}
|
||||
|
||||
public void setOrbit(ResourceLocation orbit) {
|
||||
this.orbit = orbit;
|
||||
}
|
||||
|
||||
public @Nullable ResourceLocation getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(@Nullable ResourceLocation destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public boolean isTraveling() {
|
||||
return traveling;
|
||||
}
|
||||
|
||||
public void setTraveling(boolean traveling) {
|
||||
this.traveling = traveling;
|
||||
}
|
||||
|
||||
public double getDistanceTraveled() {
|
||||
return distanceTraveled;
|
||||
}
|
||||
|
||||
public void setDistanceTraveled(double distanceTraveled) {
|
||||
this.distanceTraveled = distanceTraveled;
|
||||
}
|
||||
|
||||
public double getDistanceToDest() {
|
||||
return distanceToDest;
|
||||
}
|
||||
|
||||
public void setDistanceToDest(double distanceToDest) {
|
||||
this.distanceToDest = distanceToDest;
|
||||
}
|
||||
|
||||
public void travel(double distance) {
|
||||
distanceTraveled = Math.min( distanceTraveled + distance, distanceToDest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
|
||||
PartitionData that = (PartitionData) obj;
|
||||
|
||||
return Objects.equals(this.orbit, that.orbit)
|
||||
&& Objects.equals(this.destination, that.destination)
|
||||
&& this.traveling == that.traveling
|
||||
&& Double.compare(this.distanceTraveled, that.distanceTraveled) == 0
|
||||
&& Double.compare(this.distanceToDest, that.distanceToDest) == 0;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package net.xevianlight.aphelion.core.space;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SpacePartitionSavedData extends SavedData {
|
||||
|
||||
private static final String NAME = "aphelion_station_partitions";
|
||||
|
||||
private final Long2ObjectMap<ResourceLocation> map = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public static SpacePartitionSavedData create() {
|
||||
return new SpacePartitionSavedData();
|
||||
}
|
||||
|
||||
public static SpacePartitionSavedData load(CompoundTag tag, HolderLookup.Provider lookupProvider) {
|
||||
SpacePartitionSavedData data = create();
|
||||
|
||||
ListTag entires = tag.getList("Entries", CompoundTag.TAG_COMPOUND);
|
||||
for (int i = 0; i < entires.size(); i++) {
|
||||
CompoundTag e = entires.getCompound(i);
|
||||
long key = e.getLong("Key");
|
||||
String orbit = e.getString("Orbit"); // "aphelion/mars"
|
||||
ResourceLocation orbitRL = ResourceLocation.tryParse(orbit);
|
||||
if (orbitRL != null)
|
||||
data.map.put(key, orbitRL);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
ListTag entries = new ListTag();
|
||||
|
||||
map.long2ObjectEntrySet().forEach(entry -> {
|
||||
CompoundTag e = new CompoundTag();
|
||||
e.putLong("Key", entry.getLongKey());
|
||||
e.putString("Orbit", entry.getValue().toString());
|
||||
entries.add(e);
|
||||
});
|
||||
|
||||
tag.put("Entries", entries);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public @Nullable ResourceLocation getOrbitForPartition(int px, int pz) {
|
||||
return map.get(pack(px, pz));
|
||||
}
|
||||
|
||||
public void setOrbitForPartition(int px, int pz, ResourceLocation orbit) {
|
||||
long key = pack(px, pz);
|
||||
ResourceLocation prev = map.get(key);
|
||||
if (!orbit.equals(prev)) {
|
||||
map.put(key, orbit);
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clearOrbitForPartition(int px, int pz) {
|
||||
long key = pack(px, pz);
|
||||
ResourceLocation removed = map.remove(key);
|
||||
if (removed != null) {
|
||||
setDirty();;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void clearAllOrbits() {
|
||||
if (!map.isEmpty()) {
|
||||
map.clear();
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void overwriteAllExistingOrbits(ResourceLocation orbit) {
|
||||
if (map.isEmpty()) return;
|
||||
|
||||
boolean changed = false;
|
||||
for (var entry : map.long2ObjectEntrySet()) {
|
||||
if(!orbit.equals(entry.getValue())) {
|
||||
entry.setValue(orbit);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) setDirty();
|
||||
}
|
||||
|
||||
public static SpacePartitionSavedData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(
|
||||
new Factory<>(SpacePartitionSavedData::create, SpacePartitionSavedData::load),
|
||||
NAME
|
||||
);
|
||||
}
|
||||
|
||||
public static long pack(int px, int pz) {
|
||||
return (((long) px) << 32) | (pz & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static int unpackX(long key) {
|
||||
return (int)(key >> 32);
|
||||
}
|
||||
|
||||
public static int unpackZ(long key) {
|
||||
return (int)key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,9 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider {
|
||||
dropSelf(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get());
|
||||
dropSelf(ModBlocks.OXYGEN_TEST_BLOCK.get());
|
||||
dropOther(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), ItemStack.EMPTY.getItem());
|
||||
dropSelf(ModBlocks.LAUNCH_PAD.get());
|
||||
dropSelf(ModBlocks.GRAVITY_TEST_BLOCK.get());
|
||||
dropSelf(ModBlocks.ROCKET_ASSEMBLER_BLOCK.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,8 +27,14 @@ public class ModBlockStateProvider extends BlockStateProvider {
|
||||
|
||||
blockWithItem(ModBlocks.BLOCK_STEEL);
|
||||
blockWithItem(ModBlocks.DIMENSION_CHANGER);
|
||||
|
||||
// this is already defined ourselves
|
||||
// blockItem(ModBlocks.LAUNCH_PAD);
|
||||
|
||||
blockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK);
|
||||
|
||||
blockWithItem(ModBlocks.OXYGEN_TEST_BLOCK);
|
||||
blockWithItem(ModBlocks.GRAVITY_TEST_BLOCK);
|
||||
}
|
||||
|
||||
private void blockWithItem(DeferredBlock<?> deferredBlock) {
|
||||
|
||||
@@ -40,5 +40,9 @@ public class ModBlockTagProvider extends BlockTagsProvider {
|
||||
|
||||
tag(ModTags.Blocks.STORAGE_BLOCKS)
|
||||
.add(ModBlocks.BLOCK_STEEL.get());
|
||||
|
||||
tag(ModTags.Blocks.LAUNCH_PAD)
|
||||
.add(ModBlocks.LAUNCH_PAD.get());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.xevianlight.aphelion.entites.vehicles;
|
||||
|
||||
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.xevianlight.aphelion.core.init.ModSounds;
|
||||
|
||||
public class RocketEngineSound extends AbstractTickableSoundInstance {
|
||||
private final RocketEntity rocket;
|
||||
|
||||
public RocketEngineSound (RocketEntity rocket) {
|
||||
super(ModSounds.ROCKET_ENGINE.get(), SoundSource.AMBIENT, SoundInstance.createUnseededRandom());
|
||||
this.rocket = rocket;
|
||||
|
||||
this.looping = true;
|
||||
this.delay = 0;
|
||||
this.volume = 1;
|
||||
this.pitch = 1.0f;
|
||||
|
||||
this.x = rocket.getX();
|
||||
this.y = rocket.getY();
|
||||
this.z = rocket.getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (rocket.isRemoved() || rocket.getPhase() != RocketEntity.FlightPhase.ASCEND) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// follow entity
|
||||
this.x = rocket.getX();
|
||||
this.y = rocket.getY();
|
||||
this.z = rocket.getZ();
|
||||
}
|
||||
|
||||
public void killSound() {
|
||||
stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.xevianlight.aphelion.entites.vehicles;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -10,7 +11,6 @@ import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ColumnPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
@@ -29,14 +29,12 @@ import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
|
||||
import net.neoforged.neoforge.fluids.FluidType;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.KeyVariables;
|
||||
import net.xevianlight.aphelion.core.init.ModEntities;
|
||||
import net.xevianlight.aphelion.util.RocketStructure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn {
|
||||
|
||||
@@ -53,12 +51,11 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
private double landingPosX;
|
||||
private double landingPosZ;
|
||||
|
||||
private static final double TELEPORT_Y = 600.0;
|
||||
private static final double ASCEND_ACCEL = 0.05;
|
||||
private static final double DESCEND_SPEED = 2;
|
||||
private static final double TELEPORT_Y = 1000.0;
|
||||
private static final double ASCEND_ACCEL = 0.0125;
|
||||
private static final double DESCEND_SPEED = 1;
|
||||
|
||||
private double yVel = 0.0;
|
||||
private boolean jumpWasDown = false;
|
||||
|
||||
private static final EntityDataAccessor<Byte> FLIGHT_PHASE =
|
||||
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.BYTE);
|
||||
@@ -101,18 +98,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
setPhase(FlightPhase.PREPARE);
|
||||
}
|
||||
|
||||
public Player getFirstPlayerPassenger() {
|
||||
if (!this.getPassengers().isEmpty()) {
|
||||
for (int i = 0; i < this.getPassengers().size(); i++) {
|
||||
if (this.getPassengers().get(i) instanceof Player player) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
@@ -146,11 +131,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
}
|
||||
|
||||
private void tickPrepare() {
|
||||
// if (targetDim == this.level().dimension()) {
|
||||
// setPhase(FlightPhase.IDLE);
|
||||
// Aphelion.LOGGER.info("Target dimension matches current dimension");
|
||||
// return;
|
||||
// }
|
||||
setPhase(FlightPhase.ASCEND);
|
||||
}
|
||||
|
||||
@@ -189,10 +169,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
landingPosZ = getZ();
|
||||
}
|
||||
|
||||
// Compute landing Y in destination
|
||||
int hx = (int) Math.floor(landingPosX);
|
||||
int hz = (int) Math.floor(landingPosZ);
|
||||
|
||||
double arrivalY = TELEPORT_Y;
|
||||
|
||||
var passengers = List.copyOf(getPassengers());
|
||||
@@ -326,26 +302,24 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
}
|
||||
}
|
||||
|
||||
private RocketEngineSound ascendLoopSound;
|
||||
|
||||
private void handleClientFlightPhaseChange(FlightPhase phase) {
|
||||
switch (phase) {
|
||||
case IDLE -> {
|
||||
// var x = 0;
|
||||
}
|
||||
case PREPARE -> {
|
||||
// var x = 1;
|
||||
case IDLE, PREPARE, TRANSIT, DESCEND, LANDED -> {
|
||||
Aphelion.LOGGER.info("Rocket state updated to {}", phase);
|
||||
if (ascendLoopSound != null) {
|
||||
ascendLoopSound.killSound();
|
||||
ascendLoopSound = null;
|
||||
}
|
||||
}
|
||||
case ASCEND -> {
|
||||
// var x = 2;
|
||||
}
|
||||
case TRANSIT -> {
|
||||
// var x = 3;
|
||||
}
|
||||
case DESCEND -> {
|
||||
// var x = 4;
|
||||
}
|
||||
case LANDED -> {
|
||||
// var x = 5;
|
||||
if (ascendLoopSound == null || ascendLoopSound.isStopped()) {
|
||||
ascendLoopSound = new RocketEngineSound(this);
|
||||
Minecraft.getInstance().getSoundManager().play(ascendLoopSound);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +356,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
landingPosX = tag.getDouble("LandingX");
|
||||
landingPosZ = tag.getDouble("LandingZ");
|
||||
yVel = tag.getDouble("yVelocity");
|
||||
|
||||
if (tag.contains("FlightPhase", Tag.TAG_BYTE)) {
|
||||
setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]);
|
||||
@@ -399,9 +374,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
|
||||
|
||||
tag.putDouble("LandingX", landingPosX);
|
||||
tag.putDouble("LandingZ", landingPosZ);
|
||||
tag.putDouble("yVelocity", yVel);
|
||||
}
|
||||
|
||||
public BlockPos getTargetPos() {
|
||||
public @Nullable BlockPos getTargetPos() {
|
||||
return targetPos;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,20 +5,22 @@ import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.handlers.ClientPayloadHandler;
|
||||
import net.neoforged.neoforge.network.registration.HandlerThread;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.BaseMultiblockDummyBlockEntity;
|
||||
import net.xevianlight.aphelion.block.dummy.entity.VAFMultiblockDummyBlockEntity;
|
||||
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.network.ClientPlayerStateUpdateHandler;
|
||||
import net.xevianlight.aphelion.network.RocketPayloadHandlers;
|
||||
import net.xevianlight.aphelion.network.ServerPayloadHandler;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionData;
|
||||
import net.xevianlight.aphelion.network.PartitionPayloadHandler;
|
||||
import net.xevianlight.aphelion.network.UpdateGravityTestBlockHandler;
|
||||
import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.network.packet.RocketLaunchPayload;
|
||||
import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID)
|
||||
public class ModBusEvents {
|
||||
@@ -40,9 +42,9 @@ public class ModBusEvents {
|
||||
.executesOn(HandlerThread.MAIN);
|
||||
|
||||
registrar.playToClient(
|
||||
PartitionData.TYPE,
|
||||
PartitionData.STREAM_CODEC,
|
||||
ServerPayloadHandler::handleDataOnMain
|
||||
PartitionPayload.TYPE,
|
||||
PartitionPayload.STREAM_CODEC,
|
||||
PartitionPayloadHandler::handleDataOnMain
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
@@ -51,5 +53,16 @@ public class ModBusEvents {
|
||||
RocketPayloadHandlers::handleRocketLaunch
|
||||
);
|
||||
|
||||
registrar.playToClient(
|
||||
ClientPlayerStateUpdatePacket.TYPE,
|
||||
ClientPlayerStateUpdatePacket.STREAM_CODEC,
|
||||
ClientPlayerStateUpdateHandler::handleDataOnMain
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
UpdateGravityTestBlockPacket.TYPE,
|
||||
UpdateGravityTestBlockPacket.STREAM_CODEC,
|
||||
UpdateGravityTestBlockHandler::handleDataOnMain
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package net.xevianlight.aphelion.mixins.common;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.attachment.AttachmentHolder;
|
||||
import net.xevianlight.aphelion.systems.GravityService;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
import org.spongepowered.asm.util.Locals;
|
||||
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin extends AttachmentHolder {
|
||||
|
||||
|
||||
@Shadow
|
||||
public float fallDistance;
|
||||
|
||||
// @At("RETURN") injects at all points IMMEDIATELY before a "return" opcode.
|
||||
// Immediately before means IMMEDIATELY BEFORE,
|
||||
// so even in a method like:
|
||||
//
|
||||
// return gravity;
|
||||
//
|
||||
// What's ACTUALLY happening is:
|
||||
//
|
||||
// $value = gravity;
|
||||
// return $value;
|
||||
//
|
||||
// So, when we inject, we get:
|
||||
//
|
||||
// $value = gravity;
|
||||
// [RETURN INJECT POINT]
|
||||
// return $value;
|
||||
//
|
||||
// so we get to edit $value.
|
||||
@Inject(method = "getGravity", at = @At("RETURN"), cancellable = true)
|
||||
public void aphelion$getGravity(CallbackInfoReturnable<Double> cir) {
|
||||
cir.setReturnValue(cir.getReturnValue() * GravityService.getGravityFactor((Entity) (Object) this));
|
||||
}
|
||||
|
||||
// Should only break if the code that increases fall distance is moved outside Entity$checkFallDamage.
|
||||
@WrapMethod(method = "checkFallDamage")
|
||||
public void aphelion$checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos, Operation<Void> original) {
|
||||
float prevFallDistance = this.fallDistance;
|
||||
|
||||
original.call(y, onGround, state, pos);
|
||||
|
||||
float newFallDistance = this.fallDistance;
|
||||
if (newFallDistance > prevFallDistance && newFallDistance > 0) {
|
||||
float fallDistanceGained = newFallDistance - prevFallDistance;
|
||||
this.fallDistance = prevFallDistance + (fallDistanceGained * Math.min(GravityService.getGravityFactor((Entity) (Object) this), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.xevianlight.aphelion.mixins.common;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.xevianlight.aphelion.systems.GravityService;
|
||||
import net.xevianlight.aphelion.systems.OxygenService;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
//
|
||||
@Mixin(LivingEntity.class)
|
||||
public abstract class LivingEntityMixin extends Entity {
|
||||
|
||||
public LivingEntityMixin(EntityType<?> type, Level level) { super(type, level); }
|
||||
|
||||
@Inject(method= "tick", at = @At("TAIL"))
|
||||
public void aphelion$tick(CallbackInfo ci) {
|
||||
if ((level() instanceof ServerLevel level)) {
|
||||
LivingEntity entity = (LivingEntity) (Object) this;
|
||||
|
||||
// Oxygen system
|
||||
OxygenService.entityTick(level, entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "travel", at = @At("HEAD"))
|
||||
public void aphelion$travel(Vec3 travelVector, CallbackInfo ci) {
|
||||
// temporarily disabled in favor of aphelion$getGravity mixin
|
||||
//if (this.isControlledByLocalInstance()) GravityService.onEntityTravel(level(), (LivingEntity) (Object) this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.xevianlight.aphelion.network;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.GravityData;
|
||||
import net.xevianlight.aphelion.systems.GravityService;
|
||||
import net.xevianlight.aphelion.systems.OxygenService;
|
||||
|
||||
/// Read-only player state object; updated by a server packet every so often
|
||||
public record ClientPlayerState(boolean oxygen, float gravity, float temperature) {
|
||||
// Default player state
|
||||
private static ClientPlayerState localData = new ClientPlayerState(true, GravityData.DEFAULT_GRAVITY * 0.5f, 50f);
|
||||
|
||||
public static void updateState(ClientPlayerState newData) {
|
||||
onStateUpdate(localData, newData);
|
||||
localData = newData;
|
||||
}
|
||||
|
||||
public static ClientPlayerState getServerStateOf(Player player) {
|
||||
return new ClientPlayerState(OxygenService.hasOxygen(player), GravityService.getGravityAccel(player), 50f);
|
||||
}
|
||||
|
||||
/// For things like playing SFX, VFX, etc. etc.
|
||||
public static void onStateUpdate(ClientPlayerState oldData, ClientPlayerState newData) {
|
||||
// TODO: add sfx
|
||||
if (!oldData.oxygen() && newData.oxygen()) {
|
||||
// On oxygen gained
|
||||
}
|
||||
if (oldData.oxygen() && !newData.oxygen()) {
|
||||
// On oxygen removed
|
||||
}
|
||||
if (newData.gravity() - 0.25f > oldData.gravity()) {
|
||||
// On gravity increased by > 0.25
|
||||
}
|
||||
if (oldData.gravity() - 0.25f > newData.gravity()) {
|
||||
// On gravity decreased by > 0.25
|
||||
}
|
||||
}
|
||||
|
||||
public static ClientPlayerState getLocalData() {
|
||||
return localData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.xevianlight.aphelion.network;
|
||||
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket;
|
||||
|
||||
public class ClientPlayerStateUpdateHandler {
|
||||
|
||||
public static void handleDataOnMain(ClientPlayerStateUpdatePacket packet, IPayloadContext context) {
|
||||
context.enqueueWork(() -> ClientPlayerState.updateState(new ClientPlayerState(packet.oxygen(), packet.gravity(), packet.temp())));
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,16 @@ package net.xevianlight.aphelion.network;
|
||||
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.entites.vehicles.RocketEntity;
|
||||
import net.xevianlight.aphelion.network.packet.ClientPlayerStateUpdatePacket;
|
||||
import net.xevianlight.aphelion.network.packet.RocketLaunchPayload;
|
||||
|
||||
import net.xevianlight.aphelion.client.AphelionClient;
|
||||
@@ -22,7 +26,22 @@ public final class KeyNetwork {
|
||||
|
||||
// consumeClick makes it fire once per press, not every tick held
|
||||
if (AphelionClient.ROCKET_LAUNCH_KEY.consumeClick() && mc.player.getVehicle() instanceof RocketEntity rocket) {
|
||||
|
||||
// Send a packet to the server telling it to try launching the rocket matching this id. The packet only contains the rocketId as an integer.
|
||||
PacketDistributor.sendToServer(new RocketLaunchPayload(rocket.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(ServerTickEvent.Post event) {
|
||||
int FREQ = 4;
|
||||
for (ServerPlayer p : event.getServer().getPlayerList().getPlayers()) {
|
||||
if (p.tickCount % FREQ == 0) {
|
||||
ClientPlayerState state = ClientPlayerState.getServerStateOf(p);
|
||||
|
||||
PacketDistributor.sendToPlayer(p, new ClientPlayerStateUpdatePacket(state.oxygen(), state.gravity(), state.temperature()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ package net.xevianlight.aphelion.network;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.PartitionClientState;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
|
||||
// Handle packets TO the client FROM the server
|
||||
public class ServerPayloadHandler {
|
||||
public class PartitionPayloadHandler {
|
||||
|
||||
public static void handleDataOnMain(PartitionData data, IPayloadContext context) {
|
||||
public static void handleDataOnMain(PartitionPayload data, IPayloadContext context) {
|
||||
// Set our local partition state to the packet we just received.
|
||||
PartitionClientState.set(data);
|
||||
Aphelion.LOGGER.info("Partition packet received! id={}", data.id());
|
||||
Aphelion.LOGGER.info("Partition packet received! id={}", data.partitionData());
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,21 @@ package net.xevianlight.aphelion.network;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionData;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID)
|
||||
public final class PartitionSync {
|
||||
|
||||
// send once right after join (safe: delayed to next server tick)
|
||||
private static final Set<UUID> PENDING_JOIN_SEND = new HashSet<>();
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLogin(PlayerEvent.PlayerLoggedInEvent e) {
|
||||
if (e.getEntity() instanceof ServerPlayer sp) {
|
||||
PENDING_JOIN_SEND.add(sp.getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
private static final java.util.Map<UUID, PartitionData> LAST_SENT = new java.util.HashMap<>();
|
||||
// Stora all packets we send to all players in a map so we can look it up later
|
||||
private static final java.util.Map<UUID, PartitionPayload> LAST_SENT = new java.util.HashMap<>();
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(ServerTickEvent.Post e) {
|
||||
@@ -39,24 +27,29 @@ public final class PartitionSync {
|
||||
// Aphelion.LOGGER.info("WORKS!!!");
|
||||
|
||||
for (ServerPlayer sp : server.getPlayerList().getPlayers()) {
|
||||
PartitionData now = computePartitionFor(sp); // your logic
|
||||
PartitionData prev = LAST_SENT.get(sp.getUUID());
|
||||
|
||||
// Prepare a new packet and compare it with the last one we sent the player
|
||||
PartitionPayload now = computePartitionFor(sp);
|
||||
PartitionPayload prev = LAST_SENT.get(sp.getUUID());
|
||||
|
||||
// If it is different, send them the new one
|
||||
if (prev == null || !prev.equals(now)) {
|
||||
PacketDistributor.sendToPlayer(sp, now);
|
||||
// Store this packet for later
|
||||
LAST_SENT.put(sp.getUUID(), now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PartitionData computePartitionFor(ServerPlayer sp) {
|
||||
// Example: convert player position to partition coords
|
||||
private static PartitionPayload computePartitionFor(ServerPlayer sp) {
|
||||
int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE);
|
||||
int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE);
|
||||
|
||||
var orbit = SpacePartitionSavedData.get(sp.serverLevel()).getOrbitForPartition(px, pz);
|
||||
String orbitId = (orbit != null) ? orbit.toString() : "aphelion:orbit/default";
|
||||
PartitionData live = SpacePartitionSavedData.get(sp.serverLevel()).getData(px, pz);
|
||||
|
||||
return new PartitionData(orbitId);
|
||||
// snapshot so mutations later don’t affect cached payloads
|
||||
PartitionData snapshot = (live == null) ? null : new PartitionData(live);
|
||||
|
||||
return new PartitionPayload(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.xevianlight.aphelion.network;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity;
|
||||
import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket;
|
||||
|
||||
public class UpdateGravityTestBlockHandler {
|
||||
public static void handleDataOnMain(UpdateGravityTestBlockPacket packet, IPayloadContext context) {
|
||||
context.enqueueWork(() -> {
|
||||
BlockPos pos = packet.pos();
|
||||
float radius = packet.radius();
|
||||
float strength = packet.strength();
|
||||
|
||||
Level level = context.player().level();
|
||||
if (level.getBlockEntity(pos) instanceof GravityTestBlockEntity blockEntity) {
|
||||
blockEntity.setRadius(radius);
|
||||
blockEntity.setStrength(strength);
|
||||
blockEntity.sendUpdate();
|
||||
level.sendBlockUpdated(pos, level.getBlockState(pos), level.getBlockState(pos), Block.UPDATE_ALL);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
public record ClientPlayerStateUpdatePacket(boolean oxygen, float gravity, float temp) implements CustomPacketPayload {
|
||||
public static final CustomPacketPayload.Type<ClientPlayerStateUpdatePacket> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "player_state_update"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, ClientPlayerStateUpdatePacket> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.BOOL,
|
||||
ClientPlayerStateUpdatePacket::oxygen,
|
||||
ByteBufCodecs.FLOAT,
|
||||
ClientPlayerStateUpdatePacket::gravity,
|
||||
ByteBufCodecs.FLOAT,
|
||||
ClientPlayerStateUpdatePacket::temp,
|
||||
ClientPlayerStateUpdatePacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
public record PartitionData (String id) implements CustomPacketPayload {
|
||||
public static final Type<PartitionData> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, PartitionData> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.STRING_UTF8,
|
||||
PartitionData::id,
|
||||
|
||||
PartitionData::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record PartitionPayload(PartitionData partitionData) implements CustomPacketPayload {
|
||||
public static final Type<PartitionPayload> TYPE =
|
||||
new Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "partition_data"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, PartitionPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
PartitionData.STREAM_CODEC,
|
||||
PartitionPayload::partitionData,
|
||||
PartitionPayload::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PartitionPayload that = (PartitionPayload) o;
|
||||
return partitionData.equals(that.partitionData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(partitionData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.xevianlight.aphelion.network.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
public record UpdateGravityTestBlockPacket(BlockPos pos, float radius, float strength) implements CustomPacketPayload {
|
||||
|
||||
public static final CustomPacketPayload.Type<UpdateGravityTestBlockPacket> TYPE =
|
||||
new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "update_oxygen_test_block"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, UpdateGravityTestBlockPacket> STREAM_CODEC = StreamCodec.composite(
|
||||
BlockPos.STREAM_CODEC,
|
||||
UpdateGravityTestBlockPacket::pos,
|
||||
ByteBufCodecs.FLOAT,
|
||||
UpdateGravityTestBlockPacket::radius,
|
||||
ByteBufCodecs.FLOAT,
|
||||
UpdateGravityTestBlockPacket::strength,
|
||||
UpdateGravityTestBlockPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,16 @@ import net.xevianlight.aphelion.util.registries.ModRegistries;
|
||||
public record Planet(
|
||||
ResourceKey<Level> dimension,
|
||||
double orbitDistance,
|
||||
ResourceKey<StarSystem> system
|
||||
ResourceKey<StarSystem> system,
|
||||
boolean oxygen,
|
||||
float gravity
|
||||
) {
|
||||
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
|
||||
Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance),
|
||||
ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system)
|
||||
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
|
||||
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),
|
||||
Codec.FLOAT.fieldOf("gravity").forGetter(Planet::gravity)
|
||||
|
||||
).apply(inst, Planet::new));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ public final class PlanetCache {
|
||||
public static final Planet DEFAULT = new Planet(
|
||||
ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")),
|
||||
1,
|
||||
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol"))
|
||||
ResourceKey.create(ModRegistries.STAR_SYSTEM, Aphelion.id("sol")),
|
||||
true,
|
||||
1
|
||||
);
|
||||
|
||||
public static void registerPlanets(Map<ResourceLocation, Planet> planets) {
|
||||
|
||||
@@ -7,6 +7,6 @@ public record StarSystem(
|
||||
int temp
|
||||
) {
|
||||
public static final Codec<StarSystem> CODEC = RecordCodecBuilder.create(inst -> inst.group(
|
||||
Codec.INT.fieldOf("dimension").forGetter(StarSystem::temp)
|
||||
Codec.INT.fieldOf("temp").forGetter(StarSystem::temp)
|
||||
).apply(inst, StarSystem::new));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package net.xevianlight.aphelion.screen;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.BrewingStandMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.neoforged.neoforge.items.SlotItemHandler;
|
||||
import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GravityTestBlockMenu extends AbstractContainerMenu {
|
||||
public final GravityTestBlockEntity blockEntity;
|
||||
private final Level level;
|
||||
|
||||
public GravityTestBlockMenu(int i, Inventory inventory, FriendlyByteBuf extraData) {
|
||||
this(i, inventory, inventory.player.level().getBlockEntity(extraData.readBlockPos()));
|
||||
}
|
||||
|
||||
public GravityTestBlockMenu(int i, Inventory inventory, BlockEntity blockEntity) {
|
||||
super(ModMenuTypes.GRAVITY_TEST_BLOCK_MENU.get(), i);
|
||||
this.blockEntity = ((GravityTestBlockEntity) blockEntity);
|
||||
this.level = inventory.player.level();
|
||||
|
||||
// Init stuff
|
||||
addPlayerInventory(inventory);
|
||||
addPlayerHotbar(inventory);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CREDIT GOES TO: diesieben07 | https://github.com/diesieben07/SevenCommons
|
||||
// must assign a slot number to each of the slots used by the GUI.
|
||||
// For this container, we can see both the tile inventory's slots as well as the player inventory slots and the hotbar.
|
||||
// Each time we add a Slot to the container, it automatically increases the slotIndex, which means
|
||||
// 0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8)
|
||||
// 9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35)
|
||||
// 36 - 44 = TileInventory slots, which map to our TileEntity slot numbers 0 - 8)
|
||||
private static final int HOTBAR_SLOT_COUNT = 9;
|
||||
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
|
||||
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
|
||||
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
|
||||
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
|
||||
private static final int VANILLA_FIRST_SLOT_INDEX = 0;
|
||||
private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT;
|
||||
|
||||
// THIS YOU HAVE TO DEFINE!
|
||||
private static final int TE_INVENTORY_SLOT_COUNT = 0; // must be the number of slots you have!
|
||||
@Override
|
||||
public @NotNull ItemStack quickMoveStack(Player playerIn, int pIndex) {
|
||||
Slot sourceSlot = slots.get(pIndex);
|
||||
if (sourceSlot == null || !sourceSlot.hasItem()) return ItemStack.EMPTY; //EMPTY_ITEM
|
||||
ItemStack sourceStack = sourceSlot.getItem();
|
||||
ItemStack copyOfSourceStack = sourceStack.copy();
|
||||
|
||||
// Check if the slot clicked is one of the vanilla container slots
|
||||
if (pIndex < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) {
|
||||
// This is a vanilla container slot so merge the stack into the tile inventory
|
||||
if (!moveItemStackTo(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX
|
||||
+ TE_INVENTORY_SLOT_COUNT, false)) {
|
||||
blockEntity.sendUpdate();
|
||||
return ItemStack.EMPTY; // EMPTY_ITEM
|
||||
}
|
||||
} else if (pIndex < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) {
|
||||
// This is a TE slot so merge the stack into the players inventory
|
||||
if (!moveItemStackTo(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) {
|
||||
blockEntity.sendUpdate();
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
} else {
|
||||
System.out.println("Invalid slotIndex:" + pIndex);
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
// If stack size == 0 (the entire stack was moved) set slot contents to null
|
||||
if (sourceStack.getCount() == 0) {
|
||||
sourceSlot.set(ItemStack.EMPTY);
|
||||
blockEntity.sendUpdate();
|
||||
} else {
|
||||
blockEntity.sendUpdate();
|
||||
sourceSlot.setChanged();
|
||||
}
|
||||
sourceSlot.onTake(playerIn, sourceStack);
|
||||
blockEntity.sendUpdate();
|
||||
return copyOfSourceStack;
|
||||
}
|
||||
|
||||
private void addPlayerInventory(Inventory playerInventory) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int l = 0; l < 9; ++l) {
|
||||
this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPlayerHotbar(Inventory playerInventory) {
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package net.xevianlight.aphelion.screen;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.StringWidget;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.gui.screens.inventory.FurnaceScreen;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket;
|
||||
|
||||
public class GravityTestBlockScreen extends AbstractContainerScreen<GravityTestBlockMenu> {
|
||||
|
||||
private static final ResourceLocation GUI_TEXTURE =
|
||||
ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "textures/gui/gravity_test_block/gui.png");
|
||||
|
||||
public GravityTestBlockScreen(GravityTestBlockMenu menu, Inventory playerInventory, Component title) {
|
||||
super(menu, playerInventory, title);
|
||||
}
|
||||
|
||||
private StringWidget gravityWidget;
|
||||
private StringWidget rangeWidget;
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
// Gets rid of labels
|
||||
this.inventoryLabelY = 73;
|
||||
this.titleLabelY = 5;
|
||||
|
||||
// Increase Gravity
|
||||
this.addRenderableWidget(Button.builder(Component.literal("+"), (button) -> {
|
||||
PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize, menu.blockEntity.gravityStrength + 0.1f));
|
||||
}).bounds(this.leftPos + 7, this.topPos + 30, 9, 9).build());
|
||||
// Decrease Gravity
|
||||
this.addRenderableWidget(Button.builder(Component.literal("-"), (button) -> {
|
||||
PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize, menu.blockEntity.gravityStrength - 0.1f));
|
||||
}).bounds(this.leftPos + 19, this.topPos + 30, 9, 9).build());
|
||||
// Increase Radius
|
||||
this.addRenderableWidget(Button.builder(Component.literal("+"), (button) -> {
|
||||
PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize + 1, menu.blockEntity.gravityStrength));
|
||||
}).bounds(this.leftPos + 135, this.topPos + 32, 9, 9).build());
|
||||
// Decrease Radius
|
||||
this.addRenderableWidget(Button.builder(Component.literal("-"), (button) -> {
|
||||
PacketDistributor.sendToServer(new UpdateGravityTestBlockPacket(menu.blockEntity.getBlockPos(), menu.blockEntity.areaSize - 1, menu.blockEntity.gravityStrength));
|
||||
}).bounds(this.leftPos + 147, this.topPos + 32, 9, 9).build());
|
||||
|
||||
// Current Gravity
|
||||
gravityWidget = new StringWidget(
|
||||
this.leftPos + 11,
|
||||
this.topPos+46,
|
||||
26,
|
||||
9,
|
||||
Component.literal("" + menu.blockEntity.gravityStrength),
|
||||
this.font);
|
||||
this.addRenderableWidget(gravityWidget);
|
||||
|
||||
// Current Radius
|
||||
rangeWidget = new StringWidget(
|
||||
this.leftPos + 139,
|
||||
this.topPos+46,
|
||||
26,
|
||||
9,
|
||||
Component.literal("" + menu.blockEntity.areaSize),
|
||||
this.font);
|
||||
this.addRenderableWidget(rangeWidget);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics pGuiGraphics, float pPartialTick, int pMouseX, int pMouseY) {
|
||||
RenderSystem.setShader(GameRenderer::getPositionTexShader);
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
RenderSystem.setShaderTexture(0, GUI_TEXTURE);
|
||||
int x = (width - imageWidth) / 2;
|
||||
int y = (height - imageHeight) / 2;
|
||||
|
||||
pGuiGraphics.blit(GUI_TEXTURE, x, y, 0, 0, imageWidth, imageHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) {
|
||||
renderBackground(guiGraphics, mouseX, mouseY, delta);
|
||||
super.render(guiGraphics, mouseX, mouseY, delta);
|
||||
renderTooltip(guiGraphics, mouseX, mouseY);
|
||||
gravityWidget.setMessage(Component.literal(String.format("%.1f", menu.blockEntity.gravityStrength)));
|
||||
rangeWidget.setMessage(Component.literal("" + menu.blockEntity.areaSize));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import net.neoforged.neoforge.registries.DeferredHolder;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class ModMenuTypes {
|
||||
public static final DeferredRegister<MenuType<?>> MENUS =
|
||||
DeferredRegister.create(Registries.MENU, Aphelion.MOD_ID);
|
||||
@@ -23,6 +25,9 @@ public class ModMenuTypes {
|
||||
public static DeferredHolder<MenuType<?>,MenuType<VacuumArcFurnaceMenu>> VACUUM_ARC_FURNACE_MENU =
|
||||
registerMenuType("vacuum_arc_furnace_menu", VacuumArcFurnaceMenu::new);
|
||||
|
||||
public static DeferredHolder<MenuType<?>,MenuType<GravityTestBlockMenu>> GRAVITY_TEST_BLOCK_MENU =
|
||||
registerMenuType("gravity_test_block_menu", GravityTestBlockMenu::new);
|
||||
|
||||
private static <T extends AbstractContainerMenu>DeferredHolder<MenuType<?>, MenuType<T>> registerMenuType(String name,
|
||||
IContainerFactory<T> factory) {
|
||||
return MENUS.register(name, () -> IMenuTypeExtension.create(factory));
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package net.xevianlight.aphelion.systems;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.xevianlight.aphelion.network.ClientPlayerState;
|
||||
import net.xevianlight.aphelion.core.saveddata.GravitySavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.GravityData;
|
||||
|
||||
public class GravityService {
|
||||
|
||||
public static void setGravityArea(ServerLevel level, BlockPos pos, float accel, int radius) {
|
||||
GravitySavedData.get(level).setGravityRegion(pos, new GravityData(accel, radius));
|
||||
}
|
||||
|
||||
public static void removeGravityArea(ServerLevel level, BlockPos pos) {
|
||||
GravitySavedData.get(level).removeGravityRegion(pos);
|
||||
}
|
||||
|
||||
public static float getGravityAccel(Level level, BlockPos pos) {
|
||||
if (level.isClientSide) {
|
||||
// Pull from the client data b/c we can't access the server's saved data
|
||||
return ClientPlayerState.getLocalData().gravity();
|
||||
}
|
||||
// TODO: maybe change this based on how stuff pans out
|
||||
var gravity = GravitySavedData.get((ServerLevel) level).getGravityMax(pos);
|
||||
|
||||
return gravity;
|
||||
}
|
||||
|
||||
public static float getGravityAccel(Entity entity) {
|
||||
// Not sure if this is at the entity's feet, head, or the middle... research later
|
||||
// Blockpos is from entity's feet ~Xev
|
||||
BlockPos entityBlockPos = BlockPos.containing(entity.getX(), entity.getY(), entity.getZ());
|
||||
return getGravityAccel(entity.level(), entityBlockPos);
|
||||
}
|
||||
|
||||
public static float getGravityFactor(Entity entity) {
|
||||
float gravityAccelReal = getGravityAccel(entity);
|
||||
|
||||
// How many times normal gravity you're experiencing.
|
||||
// "normal gravity" varies across different entities. Thankfully, minecraft slaps a "protected" status
|
||||
// on LivingEntity.getGravity(), so i graciously get to go fuck myself and not care.
|
||||
// Players are 0.08 units/second/travel() of gravity (from what i've gathered)
|
||||
return gravityAccelReal / GravityData.ONE_G;
|
||||
}
|
||||
|
||||
/// Called by LivingEntity$travel mixin
|
||||
public static void onEntityTravel(Level level, LivingEntity entity) {
|
||||
if (
|
||||
entity.isFallFlying() || entity.isInLiquid() ||
|
||||
entity.isUnderWater() ||
|
||||
entity.hasEffect(MobEffects.SLOW_FALLING)
|
||||
) return;
|
||||
|
||||
float gravityAccelReal = getGravityAccel(entity);
|
||||
|
||||
// How many times normal gravity you're experiencing.
|
||||
// "normal gravity" varies across different entities. Thankfully, minecraft slaps a "protected" status
|
||||
// on LivingEntity.getGravity(), so i graciously get to go fuck myself and not care.
|
||||
// Players are 0.08 units/second/travel() of gravity (from what i've gathered)
|
||||
float gravityFactor = gravityAccelReal / GravityData.ONE_G;
|
||||
|
||||
// NOTE: this might cause certain entities to fly into the stratosphere at ultra low gravity,
|
||||
// seeing as this isn't the same for all entities.
|
||||
// Thankfully, though, this should have no effect on anything at default gravity.
|
||||
float baseGameGravityAccel = 0.08f;
|
||||
float translatedAccel = baseGameGravityAccel * gravityFactor;
|
||||
|
||||
Vec3 currentVelocity = entity.getDeltaMovement();
|
||||
// add baseGameGravity to cancel normal gravity, then subtract the new gravity
|
||||
if (translatedAccel > 0) entity.setDeltaMovement(currentVelocity.x(), currentVelocity.y() + (baseGameGravityAccel - translatedAccel), currentVelocity.z());
|
||||
else entity.setDeltaMovement(currentVelocity.x(), currentVelocity.y() + baseGameGravityAccel, currentVelocity.z());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.xevianlight.aphelion.systems;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.core.init.ModDamageSources;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
|
||||
public class OxygenService {
|
||||
public static boolean hasOxygen(Level level, BlockPos pos) {
|
||||
if (level.isClientSide) {
|
||||
// We can't pull oxygen data from the client side, so just, uhh, don't!
|
||||
Aphelion.LOGGER.warn("Tried to get server oxygen data from client side!");
|
||||
return false;
|
||||
}
|
||||
boolean positionHasOxygen = EnvironmentSavedData.get((ServerLevel) level).hasOxygen(level, pos);
|
||||
|
||||
return positionHasOxygen;
|
||||
}
|
||||
|
||||
public static boolean hasOxygen(Entity entity) {
|
||||
// Not sure if this is at the entity's feet, head, or the middle... research later
|
||||
// Blockpos is from entity's feet ~Xev
|
||||
BlockPos entityBlockPos = BlockPos.containing(entity.getX(), entity.getY(), entity.getZ());
|
||||
return hasOxygen(entity.level(), entityBlockPos);
|
||||
}
|
||||
|
||||
public static int OXYGEN_DAMAGE_TICK_AMT = 2;
|
||||
public static int OXYGEN_DAMAGE_TICK_FREQ = 20;
|
||||
/// Called by LivingEntity.entityTick mixin
|
||||
public static void entityTick(ServerLevel level, LivingEntity entity) {
|
||||
if (entity.tickCount % OXYGEN_DAMAGE_TICK_FREQ != 0) return;
|
||||
if (hasOxygen(entity)) return;
|
||||
entity.hurt(ModDamageSources.create(level, ModDamageSources.OXYGEN), OXYGEN_DAMAGE_TICK_AMT);
|
||||
}
|
||||
}
|
||||
114
src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java
Normal file
114
src/main/java/net/xevianlight/aphelion/util/FloodFill3D.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package net.xevianlight.aphelion.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class FloodFill3D {
|
||||
|
||||
private static final Direction[] DIRECTIONS = Direction.values();
|
||||
|
||||
public static final SolidBlockPredicate TEST_FULL_SEAL = (level, pos, state, positions, queue, direction) -> {
|
||||
if (state.isAir()) return true;
|
||||
if (state.is(ModTags.Blocks.PASSES_FLOOD_FILL)) return true;
|
||||
if (state.is(ModTags.Blocks.BLOCKS_FLOOD_FILL)) return false;
|
||||
if (state.isCollisionShapeFullBlock(level, pos)) return false;
|
||||
|
||||
VoxelShape collisionShape = state.getCollisionShape(level, pos);
|
||||
|
||||
if (collisionShape.isEmpty()) return true;
|
||||
if (!isSideSolid(collisionShape, direction)) return true;
|
||||
if (!isFaceSturdy(collisionShape, direction) && !isFaceSturdy(collisionShape, direction.getOpposite())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the other directions to find a potential path for the partial block.
|
||||
for (Direction dir : DIRECTIONS) {
|
||||
if (dir.getAxis() == direction.getAxis()) continue;
|
||||
var adjacentPos = pos.relative(dir);
|
||||
var adjacentState = level.getBlockState(adjacentPos);
|
||||
if (adjacentState.isAir()) return true;
|
||||
}
|
||||
|
||||
positions.add(pos.asLong());
|
||||
return false;
|
||||
};
|
||||
|
||||
public static Set<BlockPos> run(Level level, BlockPos start, int limit, SolidBlockPredicate predicate, boolean retainOrder) {
|
||||
level.getProfiler().push("adastra-floodfill");
|
||||
|
||||
LongSet positions = retainOrder ? new LongLinkedOpenHashSet(limit) : new LongOpenHashSet(limit);
|
||||
LongArrayFIFOQueue queue = new LongArrayFIFOQueue(limit);
|
||||
queue.enqueue(start.asLong());
|
||||
|
||||
while (!queue.isEmpty() && positions.size() < limit) {
|
||||
long packedPos = queue.dequeueLong();
|
||||
if (positions.contains(packedPos)) continue;
|
||||
positions.add(packedPos);
|
||||
|
||||
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(BlockPos.getX(packedPos), BlockPos.getY(packedPos), BlockPos.getZ(packedPos));
|
||||
for (Direction direction : DIRECTIONS) {
|
||||
pos.set(packedPos);
|
||||
pos.move(direction);
|
||||
BlockState state = level.getBlockState(pos);
|
||||
if (!predicate.test(level, pos, state, positions, queue, direction)) continue;
|
||||
queue.enqueue(pos.asLong());
|
||||
}
|
||||
}
|
||||
|
||||
Set<BlockPos> result = retainOrder ? new LinkedHashSet<>(positions.size()) : new HashSet<>(positions.size());
|
||||
for (long pos : positions) {
|
||||
result.add(BlockPos.of(pos));
|
||||
}
|
||||
|
||||
level.getProfiler().pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isSideSolid(VoxelShape collisionShape, Direction dir) {
|
||||
return switch (dir.getAxis()) {
|
||||
case X -> isAxisCovered(collisionShape, Direction.Axis.Y, Direction.Axis.Z);
|
||||
case Y -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Z);
|
||||
case Z -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Y);
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isAxisCovered(VoxelShape shape, Direction.Axis axis1, Direction.Axis axis2) {
|
||||
return shape.min(axis1) <= 0 && shape.max(axis1) >= 1 && shape.min(axis2) <= 0 && shape.max(axis2) >= 1;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isFaceSturdy(VoxelShape collisionShape, Direction dir) {
|
||||
VoxelShape faceShape = collisionShape.getFaceShape(dir);
|
||||
if (faceShape.isEmpty()) return true;
|
||||
var aabbs = faceShape.toAabbs();
|
||||
if (aabbs.isEmpty()) return true;
|
||||
return checkBounds(aabbs.get(0), dir.getAxis());
|
||||
}
|
||||
|
||||
private static boolean checkBounds(AABB bounds, Direction.Axis axis) {
|
||||
return switch (axis) {
|
||||
case X -> bounds.minY <= 0 && bounds.maxY >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1;
|
||||
case Y -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1;
|
||||
case Z -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minY <= 0 && bounds.maxY >= 1;
|
||||
};
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SolidBlockPredicate {
|
||||
|
||||
boolean test(Level level, BlockPos pos, BlockState state, LongSet positions, LongArrayFIFOQueue queue, Direction direction);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@ public class ModTags {
|
||||
public static final TagKey<Block> STORAGE_BLOCKS = commonTag("storage_blocks");
|
||||
public static final TagKey<Block> STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel");
|
||||
|
||||
public static final TagKey<Block> LAUNCH_PAD = createTag("launch_pad");
|
||||
public static final TagKey<Block> PASSES_FLOOD_FILL = createTag("passes_flood_fill");
|
||||
public static final TagKey<Block> BLOCKS_FLOOD_FILL = createTag("blocks_flood_fill");
|
||||
|
||||
private static TagKey<Block> commonTag(String name) {
|
||||
return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name));
|
||||
}
|
||||
@@ -25,7 +29,6 @@ public class ModTags {
|
||||
public static class Items {
|
||||
public static final TagKey<Item> TEST_TAG = createTag("test_tag");
|
||||
public static final TagKey<Item> INGOTS = commonTag("ingots");
|
||||
|
||||
public static final TagKey<Item> STORAGE_BLOCKS = commonTag("storage_blocks");
|
||||
public static final TagKey<Item> STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel");
|
||||
public static final TagKey<Item> INGOT_ALUMINUM = commonTag("ingots/aluminum");
|
||||
|
||||
@@ -2,7 +2,9 @@ package net.xevianlight.aphelion.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
@@ -135,4 +137,37 @@ public final class RocketStructure {
|
||||
|
||||
return new Extents(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public static RocketStructure capture(Level level, BlockPos origin, int rx, int ry, int rz) {
|
||||
return new RocketStructure(s -> {
|
||||
for (int dy = -ry; dy <= ry; dy++) {
|
||||
for (int dx = -rx; dx <= rx; dx++) {
|
||||
for (int dz = -rz; dz <= rz; dz++) {
|
||||
BlockPos p = origin.offset(dx, dy, dz);
|
||||
BlockState st = level.getBlockState(p);
|
||||
|
||||
// Skip air and unbreakables/forbidden blocks as you like
|
||||
if (st.isAir()) continue;
|
||||
|
||||
// Optional: ignore the assembler block itself
|
||||
// if (p.equals(origin)) continue;
|
||||
|
||||
s.add(dx, dy, dz, st);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void clearCaptured(Level level, BlockPos origin, RocketStructure struct) {
|
||||
for (int i = 0; i < struct.size(); i++) {
|
||||
int packed = struct.packedPosAt(i);
|
||||
int dx = RocketStructure.unpackX(packed);
|
||||
int dy = RocketStructure.unpackY(packed);
|
||||
int dz = RocketStructure.unpackZ(packed);
|
||||
|
||||
BlockPos wp = origin.offset(dx, dy, dz);
|
||||
level.setBlock(wp, Blocks.AIR.defaultBlockState(), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
@@ -0,0 +1,189 @@
|
||||
package net.xevianlight.aphelion.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Standalone flood-fill utility.
|
||||
* - Traverses blocks starting from origin
|
||||
* - Visits only positions allowed by Passable predicate
|
||||
* - Bounded by maxRange (Manhattan distance)
|
||||
* - Returns all visited positions (excluding origin by default)
|
||||
*/
|
||||
public final class TechnoFloodFill {
|
||||
|
||||
private TechnoFloodFill() {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Passable {
|
||||
boolean test(Level level, BlockPos pos, BlockState state);
|
||||
}
|
||||
|
||||
/** Convenience predicate: treat air as passable. */
|
||||
public static final Passable AIR_ONLY = (level, pos, state) -> state.isAir();
|
||||
|
||||
/**
|
||||
* Runs a bounded flood fill.
|
||||
*
|
||||
* @param level the world
|
||||
* @param origin starting position (typically BE position)
|
||||
* @param maxRange Manhattan range limit (|dx|+|dy|+|dz|)
|
||||
* @param passable which blocks can be entered/added
|
||||
* @param includeOrigin whether to include origin in the returned list
|
||||
*/
|
||||
public static List<BlockPos> run(Level level, BlockPos origin, int maxRange, Passable passable, boolean includeOrigin) {
|
||||
if (level == null) return List.of();
|
||||
|
||||
// Choose a grid size big enough to cover maxRange in all axes.
|
||||
// We need coordinates within [-maxRange, +maxRange] around origin.
|
||||
// So size must be >= (2*maxRange + 1). Next power-of-two for cheap indexing.
|
||||
int needed = 2 * maxRange + 1;
|
||||
int sizePow2 = nextPow2(needed);
|
||||
int bits = Integer.numberOfTrailingZeros(sizePow2); // since pow2
|
||||
BigVisitedGrid seen = new BigVisitedGrid(bits, origin.getX(), origin.getY(), origin.getZ());
|
||||
|
||||
// Chunk-cached blockstate fetch (no BE state needed)
|
||||
ChunkCache chunkCache = new ChunkCache(level);
|
||||
|
||||
List<BlockPos> out = new ArrayList<>();
|
||||
Deque<BlockPos> stack = new ArrayDeque<>();
|
||||
|
||||
if (includeOrigin) {
|
||||
out.add(origin);
|
||||
}
|
||||
|
||||
// Mark origin visited so we don't bounce back into it.
|
||||
seen.add(origin.getX(), origin.getY(), origin.getZ());
|
||||
stack.push(origin);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
BlockPos from = stack.pop();
|
||||
|
||||
for (Direction d : Direction.values()) {
|
||||
BlockPos next = from.relative(d);
|
||||
|
||||
if (!inRangeManhattan(origin, next, maxRange)) continue;
|
||||
|
||||
// visited check first: cheapest early-out
|
||||
if (!seen.add(next.getX(), next.getY(), next.getZ())) continue;
|
||||
|
||||
BlockState st = chunkCache.getBlockState(next);
|
||||
if (!passable.test(level, next, st)) continue;
|
||||
|
||||
out.add(next);
|
||||
stack.push(next);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean inRangeManhattan(BlockPos a, BlockPos b, int max) {
|
||||
int dx = Math.abs(a.getX() - b.getX());
|
||||
int dy = Math.abs(a.getY() - b.getY());
|
||||
int dz = Math.abs(a.getZ() - b.getZ());
|
||||
return dx + dy + dz <= max;
|
||||
}
|
||||
|
||||
private static int nextPow2(int x) {
|
||||
// smallest power of 2 >= x, with a minimum of 8
|
||||
int v = 1;
|
||||
while (v < x) v <<= 1;
|
||||
return Math.max(v, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple chunk cache for repeated reads in flood fill.
|
||||
*/
|
||||
private static final class ChunkCache {
|
||||
private final Level level;
|
||||
private LevelChunk lastChunk;
|
||||
private int lastCx = Integer.MIN_VALUE;
|
||||
private int lastCz = Integer.MIN_VALUE;
|
||||
|
||||
private ChunkCache(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
BlockState getBlockState(BlockPos pos) {
|
||||
int cx = pos.getX() >> 4;
|
||||
int cz = pos.getZ() >> 4;
|
||||
|
||||
if (cx != lastCx || cz != lastCz || lastChunk == null) {
|
||||
lastChunk = level.getChunk(cx, cz); // may load/generate; okay for your current usage
|
||||
lastCx = cx;
|
||||
lastCz = cz;
|
||||
}
|
||||
return lastChunk.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packed visited grid centered on an origin.
|
||||
*
|
||||
* Uses an int[] with 32 bits per word:
|
||||
* - size = 2^bits (must be power-of-two)
|
||||
* - Word index layout: ((y * size) + z) * wordsPerRow + (x / 32)
|
||||
* - Bit inside the word: (x % 32)
|
||||
*/
|
||||
private static final class BigVisitedGrid {
|
||||
private final int bits;
|
||||
private final int size;
|
||||
private final int wordsPerRow;
|
||||
private final int[] words;
|
||||
private final int xOff, yOff, zOff;
|
||||
|
||||
BigVisitedGrid(int bits, int xOrigin, int yOrigin, int zOrigin) {
|
||||
if (bits < 3) throw new IllegalArgumentException("Grid too small (bits=" + bits + ")");
|
||||
if (bits > 12) throw new IllegalArgumentException("Grid too large (bits=" + bits + ")");
|
||||
|
||||
this.bits = bits;
|
||||
this.size = 1 << bits;
|
||||
|
||||
// Center origin at middle of grid
|
||||
this.xOff = -xOrigin + (size / 2);
|
||||
this.yOff = -yOrigin + (size / 2);
|
||||
this.zOff = -zOrigin + (size / 2);
|
||||
|
||||
if ((size & 31) != 0) {
|
||||
// to keep wordsPerRow integer
|
||||
throw new IllegalArgumentException("Grid size must be divisible by 32, got " + size);
|
||||
}
|
||||
|
||||
this.wordsPerRow = size >>> 5; // size / 32
|
||||
int totalWords = wordsPerRow * size * size; // (y,z) rows * x-words
|
||||
this.words = new int[totalWords];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if it was NOT previously visited and is now marked visited
|
||||
*/
|
||||
boolean add(int x, int y, int z) {
|
||||
int inX = x + xOff;
|
||||
int inY = y + yOff;
|
||||
int inZ = z + zOff;
|
||||
|
||||
// Bounds check (fast and safe)
|
||||
if ((inX | inY | inZ) < 0 || inX >= size || inY >= size || inZ >= size) {
|
||||
return false; // out of grid => treat as "already seen" to prevent expansion
|
||||
}
|
||||
|
||||
int wordIndex = ((inY * size) + inZ) * wordsPerRow + (inX >>> 5);
|
||||
int bit = 1 << (inX & 31);
|
||||
|
||||
int prev = words[wordIndex];
|
||||
if ((prev & bit) != 0) return false;
|
||||
|
||||
words[wordIndex] = prev | bit;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
|
||||
# This is an example neoforge.mods.toml file. It contains the partitionData relating to the loading mods.
|
||||
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
|
||||
# The overall format is standard TOML format, v0.5.0.
|
||||
# Note that there are a couple of TOML lists in this file.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
"package": "net.xevianlight.aphelion.mixins",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"common.EntityMixin",
|
||||
"common.LivingEntityMixin"
|
||||
],
|
||||
"client": [
|
||||
"common.ClientLevelMixin",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"variants": {
|
||||
"north=true,east=true,south=true,west=true": { "model": "aphelion:block/launch_pad/0000" },
|
||||
"north=false,east=true,south=true,west=true": { "model": "aphelion:block/launch_pad/1000" },
|
||||
"north=true,east=false,south=true,west=true": { "model": "aphelion:block/launch_pad/0100" },
|
||||
"north=true,east=true,south=false,west=true": { "model": "aphelion:block/launch_pad/0010" },
|
||||
"north=true,east=true,south=true,west=false": { "model": "aphelion:block/launch_pad/0001" },
|
||||
"north=false,east=false,south=true,west=true": { "model": "aphelion:block/launch_pad/1100" },
|
||||
"north=false,east=true,south=false,west=true": { "model": "aphelion:block/launch_pad/1010" },
|
||||
"north=false,east=true,south=true,west=false": { "model": "aphelion:block/launch_pad/1001" },
|
||||
"north=true,east=false,south=false,west=true": { "model": "aphelion:block/launch_pad/0110" },
|
||||
"north=true,east=false,south=true,west=false": { "model": "aphelion:block/launch_pad/0101" },
|
||||
"north=true,east=true,south=false,west=false": { "model": "aphelion:block/launch_pad/0011" },
|
||||
"north=false,east=false,south=false,west=true": { "model": "aphelion:block/launch_pad/1110" },
|
||||
"north=false,east=false,south=true,west=false": { "model": "aphelion:block/launch_pad/1101" },
|
||||
"north=false,east=true,south=false,west=false": { "model": "aphelion:block/launch_pad/1011" },
|
||||
"north=true,east=false,south=false,west=false": { "model": "aphelion:block/launch_pad/0111" },
|
||||
"north=false,east=false,south=false,west=false": { "model": "aphelion:block/launch_pad/1111" }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"block.aphelion.test_block": "Test Block",
|
||||
"block.aphelion.electric_arc_furnace": "Electric Arc Furnace",
|
||||
"block.aphelion.vacuum_arc_furnace_controller": "Vacuum Arc Furnace Controller",
|
||||
"block.aphelion.arc_furnace_casing": "Arc Furnace Casing",
|
||||
"block.aphelion.vaf_dummy_block": "Vacuum Arc Furnace",
|
||||
|
||||
"item.aphelion.ingot_steel": "Steel Ingot",
|
||||
@@ -28,6 +29,10 @@
|
||||
"tag.item.c.ingots.steel": "Steel Ingots",
|
||||
"tag.item.c.ingots.aluminum": "Aluminum Ingots",
|
||||
|
||||
"entity.aphelion.rocket": "Rocket",
|
||||
|
||||
"fluid_type.aphelion.oil": "Oil",
|
||||
|
||||
|
||||
"command.aphelion.station.orbit.set": "Set station (%s, %s)'s orbit to %s",
|
||||
"command.aphelion.station.orbit.get": "Station (%s, %s)'s orbit is assigned to %s",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0000",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0000"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0001",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0001"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0010",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0010"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0011",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0011"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0100",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0100"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0101",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0101"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0110",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0110"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/0111",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/0111"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1000",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1000"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1001",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1001"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1010",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1010"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1011",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1011"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1100",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1100"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1101",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1101"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1110",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1110"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_bottom_top",
|
||||
"textures": {
|
||||
"top": "aphelion:block/launch_pad/1111",
|
||||
"side": "aphelion:block/launch_pad/topbottom",
|
||||
"bottom": "aphelion:block/launch_pad/1111"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "aphelion:block/launch_pad/1111"
|
||||
}
|
||||
@@ -6,5 +6,13 @@
|
||||
"stream": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"rocket_engine": {
|
||||
"sounds": [
|
||||
{
|
||||
"name": "aphelion:rocket_engine",
|
||||
"stream": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/assets/aphelion/sounds/rocket_engine.ogg
Normal file
BIN
src/main/resources/assets/aphelion/sounds/rocket_engine.ogg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user