17 Commits

Author SHA1 Message Date
TechnoDraconic
3154d32d79 changed mixins to affect ALL entities (might cause bugs) and fixed fall damage 2026-02-08 13:36:26 -08:00
XevianLight
aaa55b087f Merge remote-tracking branch 'origin/gravity-system' into gravity-system 2026-02-07 17:04:20 -07:00
XevianLight
d9bd26e8a0 GravitySavedData now reads from Planet jsons for default values 2026-02-07 17:03:05 -07:00
TechnoDraconic
010fc7307f added some generated files to git 2026-02-07 15:43:53 -08:00
TechnoDraconic
9356c43ea7 added gravity test block with gui and debug renderer 2026-02-06 22:22:10 -08:00
TechnoDraconic
fa41966de4 added gravity 2026-02-05 00:44:15 -08:00
TechnoDraconic
3b5d10f414 made envData nonstatic again 2026-02-04 09:26:29 -08:00
XevianLight
331da7c78f Gravity data 2026-02-03 23:35:41 -07:00
XevianLight
74731444ea Merge remote-tracking branch 'origin/master' 2026-02-03 20:03:40 -07:00
XevianLight
df344034a6 BasicEntityBlock and TickableBlockEntity added to simplify block entity creation 2026-02-03 20:00:52 -07:00
TechnoDraconic
84000f31fd Added oxygen damage and an oxygen damage source 2026-02-02 09:29:16 -08:00
XevianLight
012985441f Debug rendering for oxygen (VERY LAGGY BUT WORKS) 2026-01-30 21:46:13 -07:00
XevianLight
cc93d2fb42 More EnvironmentSavedData stuff 2026-01-29 23:42:33 -07:00
XevianLight
2f0c499fdf Merge branch 'refs/heads/oxygen-system' 2026-01-29 23:12:15 -07:00
XevianLight
9b030d8c9d Merge branch 'oxygen-system'
# Conflicts:
#	src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d
#	src/generated/resources/.cache/a8139181fab9cfd94e67697230cbaca839a05e1f
#	src/main/java/net/xevianlight/aphelion/core/init/ModItems.java
2026-01-29 21:49:08 -07:00
XevianLight
b012528247 Environment Data saving and Floodfill. Added additional data to PartitionPayload and PartitionData for later 2026-01-29 21:43:18 -07:00
XevianLight
f3bd3f891a Rocket landing pad block. Added comments. Adjusted rocket values 2026-01-26 21:31:03 -07:00
123 changed files with 3377 additions and 368 deletions

View File

@@ -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 69d8318ddba171526d1fabb87d9d93548ed8598e data/aphelion/loot_table/blocks/arc_furnace_casing.json
05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json 05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json
ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json
b63130d9c10485676303d729807b6fcaac080294 data/aphelion/loot_table/blocks/electric_arc_furnace.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 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 1ab50c99e9f478840b9d003fd56ebdcab12fbbce data/aphelion/loot_table/blocks/test_block.json
7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json 7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json
797bf9839d79e08b4832c9eaf3cb303b0471ed0c data/aphelion/loot_table/blocks/vaf_dummy_block.json 797bf9839d79e08b4832c9eaf3cb303b0471ed0c data/aphelion/loot_table/blocks/vaf_dummy_block.json

View File

@@ -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.json
058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks/steel.json 058c56a0c17204ed5d9cadaffae84292b4752213 data/c/tags/block/storage_blocks/steel.json
7d420216f15b8f78d2a3b298f9bb773a9e5f79c3 data/minecraft/tags/block/mineable/pickaxe.json 7d420216f15b8f78d2a3b298f9bb773a9e5f79c3 data/minecraft/tags/block/mineable/pickaxe.json

View File

@@ -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 851ff42f7b21dec86107c8e0cefb3934ae4ebc08 assets/aphelion/blockstates/block_steel.json
30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json 30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json
cb4287104006c80c8396b290ab5258df65d62cef assets/aphelion/blockstates/electric_arc_furnace.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 28131a570d3666b7f323de4ad8a69e52ceec92e2 assets/aphelion/blockstates/oxygen_test_block.json
b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json
a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json
2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json 2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json
5f7e8674070f31a63875b5d6147153bfa0eef61a assets/aphelion/models/block/electric_arc_furnace.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 746f23f150a01524ad03cbd1eb822bfbb7cf453b assets/aphelion/models/block/oxygen_test_block.json
e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json
cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json
3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json 3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json
db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json
279080c06ada87f54fd0a7b885b256dbe25a946a assets/aphelion/models/item/electric_arc_furnace.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 24cf60e70f7d9450b0e70cf017662e80971bae17 assets/aphelion/models/item/oxygen_test_block.json
74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json 74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json
88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json 88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json

View File

@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "aphelion:block/gravity_test_block"
}
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "aphelion:block/gravity_test_block"
}
}

View File

@@ -0,0 +1,3 @@
{
"parent": "aphelion:block/gravity_test_block"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,5 @@
{
"values": [
"aphelion:launch_pad"
]
}

View File

@@ -1,26 +1,30 @@
package net.xevianlight.aphelion; package net.xevianlight.aphelion;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.Dist;
import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 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.EntityRenderersEvent;
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
import net.neoforged.neoforge.event.AddReloadListenerEvent; 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.dummy.renderer.MultiblockDummyRenderer;
import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer; import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer;
import net.xevianlight.aphelion.client.AphelionConfig; 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.planet.AphelionPlanetJSONLoader;
import net.xevianlight.aphelion.core.init.*; import net.xevianlight.aphelion.core.init.*;
import net.xevianlight.aphelion.fluid.BaseFluidType; import net.xevianlight.aphelion.fluid.BaseFluidType;
import net.xevianlight.aphelion.fluid.ModFluidTypes; import net.xevianlight.aphelion.fluid.ModFluidTypes;
import net.xevianlight.aphelion.fluid.ModFluids; import net.xevianlight.aphelion.fluid.ModFluids;
import net.xevianlight.aphelion.recipe.ModRecipes; import net.xevianlight.aphelion.recipe.ModRecipes;
import net.xevianlight.aphelion.screen.ElectricArcFurnaceScreen; import net.xevianlight.aphelion.screen.*;
import net.xevianlight.aphelion.screen.ModMenuTypes;
import net.xevianlight.aphelion.screen.TestBlockScreen;
import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen;
import org.slf4j.Logger; import org.slf4j.Logger;
import net.xevianlight.aphelion.entites.vehicles.RocketRenderer; import net.xevianlight.aphelion.entites.vehicles.RocketRenderer;
@@ -132,7 +136,7 @@ public class Aphelion {
@SubscribeEvent @SubscribeEvent
public static void registerBER(EntityRenderersEvent.RegisterRenderers event) { public static void registerBER(EntityRenderersEvent.RegisterRenderers event) {
event.registerBlockEntityRenderer(ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), MultiblockDummyRenderer::new); 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 @SubscribeEvent
@@ -140,11 +144,18 @@ public class Aphelion {
event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new); event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new);
event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new); event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new);
event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new); event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new);
event.register(ModMenuTypes.GRAVITY_TEST_BLOCK_MENU.get(), GravityTestBlockScreen::new);
} }
@SubscribeEvent @SubscribeEvent
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new); 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);
}
} }
} }

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -1,25 +1,53 @@
package net.xevianlight.aphelion.block.custom; package net.xevianlight.aphelion.block.custom;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.EntityBlock; 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.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; 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 net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class OxygenTestBlock extends Block implements EntityBlock { public class OxygenTestBlock extends BasicEntityBlock {
public OxygenTestBlock(Properties properties) { 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 @Override
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public @Nullable BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
return new OxygenTestBlockEntity(blockPos, blockState); return new OxygenTestBlockEntity(blockPos, blockState);
} }
public static Properties getProperties() { public static Properties getProperties() {
return Properties.of(); 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);
}
} }

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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() {}
}

View File

@@ -142,7 +142,7 @@ public class BaseMultiblockDummyBlockEntity extends BlockEntity implements IMult
// Force rerender on client // Force rerender on client
if (level != null) { if (level != null) {
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); 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(); setChanged();
if (level != null) { if (level != null) {
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
requestModelDataUpdate(); // only if you use model data requestModelDataUpdate(); // only if you use model partitionData
} }
} }
} }

View File

@@ -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);
}
}

View File

@@ -1,29 +1,38 @@
package net.xevianlight.aphelion.block.entity.custom; package net.xevianlight.aphelion.block.entity.custom;
import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; 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.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.SectionPos; import net.minecraft.world.level.Level;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import static net.xevianlight.aphelion.Aphelion.LOGGER; import static net.xevianlight.aphelion.Aphelion.LOGGER;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk; 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.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 org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
import java.util.*; import java.util.*;
public class OxygenTestBlockEntity extends BlockEntity { public class OxygenTestBlockEntity extends BlockEntity implements TickableBlockEntity {
public OxygenTestBlockEntity(BlockPos pos, BlockState blockState) { public OxygenTestBlockEntity(BlockPos pos, BlockState blockState) {
super(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), pos, blockState); super(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), pos, blockState);
} }
public boolean isInitialized;
@Override
public boolean isInitialized() {
return this.isInitialized;
}
private LevelChunk lastChunk; private LevelChunk lastChunk;
private int lastChunkX = Integer.MIN_VALUE, lastChunkZ = Integer.MIN_VALUE; 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(); 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) { 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; 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 /// 256*256*256 grid of booleans
private class BigBoolGrid { private class BigBoolGrid {
int bitsSize; int bitsSize;
@@ -94,7 +117,7 @@ public class OxygenTestBlockEntity extends BlockEntity {
// wrapping order is X->Z->Y, so we go along the X axis, // 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 // wrap to the Z axis to make a square, and once the square is full
// we step up once along the Y axis. // 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); int bit = (1 << bitPos);
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y // 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; private Set<BlockPos> enclosedCache;
public List<BlockPos> getEnclosedBlocks() {
if (level == null) return List.of();
if (enclosedCache != null) return 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(); long start = System.nanoTime();
List<BlockPos> enclosedBlocks = new ArrayList<>(); Set<BlockPos> enclosedBlocks = FloodFill3D.run(level, getBlockPos(), 6000, FloodFill3D.TEST_FULL_SEAL, true);
// 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? // // make this bitch BIIIIIG
// maybe a bit more, IDK how exactly it scales to blocks. // BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
Stack<BlockPos> stack = new Stack<>(); //
Stack<Integer> radiusStack = new Stack<>(); // // 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.add(this.getBlockPos()); // Stack<BlockPos> stack = new Stack<>();
// Stack<Integer> radiusStack = new Stack<>();
//
// stack.add(this.getBlockPos());
// Do flood fill out from this block // Do flood fill out from this block
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest). // 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... // and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
BlockPos ourPos = getBlockPos(); // BlockPos ourPos = getBlockPos();
while (!stack.isEmpty()) { // while (!stack.isEmpty()) {
BlockPos spreadFromPos = stack.pop(); // BlockPos spreadFromPos = stack.pop();
for (Direction d : Direction.values()) { // for (Direction d : Direction.values()) {
BlockPos relativePos = spreadFromPos.relative(d); // 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)) { var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
// seen.add runs seen.contains under the hood, if (singleplayerServer != null) {
// + this is the most expensive operation. var serverLevel = singleplayerServer.getLevel(level.dimension());
// should save a lot of time! if (serverLevel != null) {
if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
enclosedBlocks.add(relativePos); // Build a set of longs for the newly computed blocks (order-independent)
stack.add(relativePos); 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; 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()); LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
enclosedCache = enclosedBlocks; 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() { private void helper() {
var myVar = new BlockPos(1, 1, 1).hashCode(); 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;
}
} }

View File

@@ -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;
}
}

View File

@@ -12,6 +12,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity; import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
@@ -23,7 +24,10 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {} public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) { private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
return be.getEnclosedBlocks(); // cache = be.getEnclosedBlocks();
// if (cache != null)
// return cache;
return List.of();
} }
@Override @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); 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; private Set<BlockPos> relativePositionsCache;
@Override @Override
// If in debug mode, renders a model made from the blocks // If in debug mode, renders a model made from the blocks
// that are currently returned by toBlockPositions(OxygenTestBlockEntity). // that are currently returned by toBlockPositions(OxygenTestBlockEntity).
public void render(OxygenTestBlockEntity be, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) { 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! // This bit's debug only, folks!
if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return; 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 // Renderers are relative to our block pos, so transform all points to be relative to block pos as well
List<BlockPos> positionsToRender = toBlockPositions(be); List<BlockPos> positionsToRender = toBlockPositions(be);
BlockPos originPos = be.getBlockPos(); BlockPos originPos = be.getBlockPos();
if (true) return; // if (true) return;
Set<BlockPos> relativePositions; Set<BlockPos> relativePositions;
if (relativePositionsCache != null) relativePositions = relativePositionsCache; if (relativePositionsCache != null) relativePositions = relativePositionsCache;

View File

@@ -2,6 +2,8 @@ package net.xevianlight.aphelion.client;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation; 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.minecraft.world.level.dimension.DimensionType;
import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; 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.DimensionRenderer;
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache; import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects; 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; import net.xevianlight.aphelion.util.SpacePartitionHelper;
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT) @EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
@@ -50,5 +53,13 @@ public class AphelionDebugOverlay {
event.getLeft().add(" Orbit: " + orbitId); event.getLeft().add(" Orbit: " + orbitId);
// event.getLeft().add(" Sky: " + rendererSummary); // event.getLeft().add(" Sky: " + rendererSummary);
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z)); 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()));
}
} }
} }

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,20 +1,29 @@
package net.xevianlight.aphelion.client; 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; import java.util.Optional;
public final class PartitionClientState { 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); return Optional.ofNullable(last);
} }
public static String idOrUnknown() { 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) { // public static int pxOr(int fallback) {

View File

@@ -71,7 +71,7 @@ public class DimensionSkyEffects extends DimensionSpecialEffects {
// int py = PartitionClientState.pyOr(0); // int py = PartitionClientState.pyOr(0);
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown()); 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; if (data != null) return data;
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");

View File

@@ -5,12 +5,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.DimensionSpecialEffects; import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState; import net.xevianlight.aphelion.client.PartitionClientState;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData;
import net.xevianlight.aphelion.util.SpacePartitionHelper; import net.xevianlight.aphelion.util.SpacePartitionHelper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f; import org.joml.Matrix4f;
@@ -29,7 +26,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
return ResourceLocation.withDefaultNamespace("overworld"); return ResourceLocation.withDefaultNamespace("overworld");
} }
if (effectsId.equals(Aphelion.id("space"))) { 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; return effectsId;
} }
@@ -80,7 +77,7 @@ public class SpaceSkyEffects extends DimensionSpecialEffects {
// int py = PartitionClientState.pyOr(0); // int py = PartitionClientState.pyOr(0);
var data = ResourceLocation.parse(PartitionClientState.idOrUnknown()); 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; if (data != null) return data;
return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default"); return ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "orbit/default");

View File

@@ -15,17 +15,18 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.*; import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ColumnPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.RelativeMovement; import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.xevianlight.aphelion.Aphelion; 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.entites.vehicles.RocketEntity;
import net.xevianlight.aphelion.planet.Planet;
import net.xevianlight.aphelion.util.RocketStructure; import net.xevianlight.aphelion.util.RocketStructure;
import net.xevianlight.aphelion.util.SpacePartitionHelper; import net.xevianlight.aphelion.util.SpacePartitionHelper;
import net.xevianlight.aphelion.util.registries.ModRegistries;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.EnumSet; 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("planet")
.then(Commands.literal("tp") .then(Commands.literal("tp")

View File

@@ -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);
}
}
}

View File

@@ -46,4 +46,14 @@ public class ModBlockEntities {
BLOCK_ENTITIES.register("oxygen_test_block_entity", () -> BlockEntityType.Builder.of( BLOCK_ENTITIES.register("oxygen_test_block_entity", () -> BlockEntityType.Builder.of(
OxygenTestBlockEntity::new, ModBlocks.OXYGEN_TEST_BLOCK.get()).build(null) 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)
);
} }

View File

@@ -6,16 +6,20 @@ import net.neoforged.neoforge.registries.DeferredRegister;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.custom.*; import net.xevianlight.aphelion.block.custom.*;
import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock; import net.xevianlight.aphelion.block.dummy.VAFMultiblockDummyBlock;
import net.xevianlight.aphelion.block.entity.custom.GravityTestBlockEntity;
public class ModBlocks { public class ModBlocks {
public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(Aphelion.MOD_ID); 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> 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> 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> 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> 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> 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> 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> 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> 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()));
} }

View File

@@ -41,5 +41,6 @@ public class ModCreativeTabs {
output.accept(ModItems.BLOCK_STEEL); output.accept(ModItems.BLOCK_STEEL);
output.accept(ModItems.ARC_FURNACE_CASING_BLOCK); output.accept(ModItems.ARC_FURNACE_CASING_BLOCK);
output.accept(ModItems.VACUUM_ARC_FURNACE_CONTROLLER); output.accept(ModItems.VACUUM_ARC_FURNACE_CONTROLLER);
output.accept(ModItems.LAUNCH_PAD);
}).build()); }).build());
} }

View File

@@ -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));
}
}

View File

@@ -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> 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> 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> 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())); // 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()));
} }

View File

@@ -23,6 +23,8 @@ public class ModSounds {
public static final Supplier<SoundEvent> BIT_SHIFT = registerSoundEvent("bit_shift"); public static final Supplier<SoundEvent> BIT_SHIFT = registerSoundEvent("bit_shift");
public static final ResourceKey<JukeboxSong> BIT_SHIFT_KEY = createSong("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) { private static Supplier<SoundEvent> registerSoundEvent(String name) {
ResourceLocation id = ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, name); ResourceLocation id = ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, name);
return SOUND_EVENTS.register(name, () -> SoundEvent.createVariableRangeEvent(id)); return SOUND_EVENTS.register(name, () -> SoundEvent.createVariableRangeEvent(id));

View File

@@ -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; // dont 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;
});
});
}
}

View File

@@ -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
);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -25,6 +25,9 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider {
dropSelf(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get()); dropSelf(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get());
dropSelf(ModBlocks.OXYGEN_TEST_BLOCK.get()); dropSelf(ModBlocks.OXYGEN_TEST_BLOCK.get());
dropOther(ModBlocks.VAF_MULTIBLOCK_DUMMY_BLOCK.get(), ItemStack.EMPTY.getItem()); 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 @Override

View File

@@ -27,8 +27,14 @@ public class ModBlockStateProvider extends BlockStateProvider {
blockWithItem(ModBlocks.BLOCK_STEEL); blockWithItem(ModBlocks.BLOCK_STEEL);
blockWithItem(ModBlocks.DIMENSION_CHANGER); blockWithItem(ModBlocks.DIMENSION_CHANGER);
// this is already defined ourselves
// blockItem(ModBlocks.LAUNCH_PAD);
blockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK); blockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK);
blockWithItem(ModBlocks.OXYGEN_TEST_BLOCK); blockWithItem(ModBlocks.OXYGEN_TEST_BLOCK);
blockWithItem(ModBlocks.GRAVITY_TEST_BLOCK);
} }
private void blockWithItem(DeferredBlock<?> deferredBlock) { private void blockWithItem(DeferredBlock<?> deferredBlock) {

View File

@@ -40,5 +40,9 @@ public class ModBlockTagProvider extends BlockTagsProvider {
tag(ModTags.Blocks.STORAGE_BLOCKS) tag(ModTags.Blocks.STORAGE_BLOCKS)
.add(ModBlocks.BLOCK_STEEL.get()); .add(ModBlocks.BLOCK_STEEL.get());
tag(ModTags.Blocks.LAUNCH_PAD)
.add(ModBlocks.LAUNCH_PAD.get());
} }
} }

View File

@@ -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();
}
}

View File

@@ -1,5 +1,6 @@
package net.xevianlight.aphelion.entites.vehicles; package net.xevianlight.aphelion.entites.vehicles;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -10,7 +11,6 @@ import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ColumnPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand; 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.entity.IEntityWithComplexSpawn;
import net.neoforged.neoforge.fluids.FluidType; import net.neoforged.neoforge.fluids.FluidType;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.core.KeyVariables;
import net.xevianlight.aphelion.core.init.ModEntities; import net.xevianlight.aphelion.core.init.ModEntities;
import net.xevianlight.aphelion.util.RocketStructure; import net.xevianlight.aphelion.util.RocketStructure;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn { public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpawn {
@@ -53,12 +51,11 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
private double landingPosX; private double landingPosX;
private double landingPosZ; private double landingPosZ;
private static final double TELEPORT_Y = 600.0; private static final double TELEPORT_Y = 1000.0;
private static final double ASCEND_ACCEL = 0.05; private static final double ASCEND_ACCEL = 0.0125;
private static final double DESCEND_SPEED = 2; private static final double DESCEND_SPEED = 1;
private double yVel = 0.0; private double yVel = 0.0;
private boolean jumpWasDown = false;
private static final EntityDataAccessor<Byte> FLIGHT_PHASE = private static final EntityDataAccessor<Byte> FLIGHT_PHASE =
SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.BYTE); SynchedEntityData.defineId(RocketEntity.class, EntityDataSerializers.BYTE);
@@ -101,18 +98,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
setPhase(FlightPhase.PREPARE); 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 @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@@ -146,11 +131,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
} }
private void tickPrepare() { private void tickPrepare() {
// if (targetDim == this.level().dimension()) {
// setPhase(FlightPhase.IDLE);
// Aphelion.LOGGER.info("Target dimension matches current dimension");
// return;
// }
setPhase(FlightPhase.ASCEND); setPhase(FlightPhase.ASCEND);
} }
@@ -189,10 +169,6 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
landingPosZ = getZ(); landingPosZ = getZ();
} }
// Compute landing Y in destination
int hx = (int) Math.floor(landingPosX);
int hz = (int) Math.floor(landingPosZ);
double arrivalY = TELEPORT_Y; double arrivalY = TELEPORT_Y;
var passengers = List.copyOf(getPassengers()); var passengers = List.copyOf(getPassengers());
@@ -326,26 +302,24 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
} }
} }
private RocketEngineSound ascendLoopSound;
private void handleClientFlightPhaseChange(FlightPhase phase) { private void handleClientFlightPhaseChange(FlightPhase phase) {
switch (phase) { switch (phase) {
case IDLE -> { case IDLE, PREPARE, TRANSIT, DESCEND, LANDED -> {
// var x = 0; Aphelion.LOGGER.info("Rocket state updated to {}", phase);
if (ascendLoopSound != null) {
ascendLoopSound.killSound();
ascendLoopSound = null;
} }
case PREPARE -> {
// var x = 1;
} }
case ASCEND -> { case ASCEND -> {
// var x = 2; if (ascendLoopSound == null || ascendLoopSound.isStopped()) {
ascendLoopSound = new RocketEngineSound(this);
Minecraft.getInstance().getSoundManager().play(ascendLoopSound);
} }
case TRANSIT -> {
// var x = 3;
}
case DESCEND -> {
// var x = 4;
}
case LANDED -> {
// var x = 5;
} }
} }
} }
@@ -382,6 +356,7 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
landingPosX = tag.getDouble("LandingX"); landingPosX = tag.getDouble("LandingX");
landingPosZ = tag.getDouble("LandingZ"); landingPosZ = tag.getDouble("LandingZ");
yVel = tag.getDouble("yVelocity");
if (tag.contains("FlightPhase", Tag.TAG_BYTE)) { if (tag.contains("FlightPhase", Tag.TAG_BYTE)) {
setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]); setPhase(FlightPhase.values()[tag.getByte("FlightPhase")]);
@@ -399,9 +374,10 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
tag.putDouble("LandingX", landingPosX); tag.putDouble("LandingX", landingPosX);
tag.putDouble("LandingZ", landingPosZ); tag.putDouble("LandingZ", landingPosZ);
tag.putDouble("yVelocity", yVel);
} }
public BlockPos getTargetPos() { public @Nullable BlockPos getTargetPos() {
return targetPos; return targetPos;
} }

View File

@@ -5,20 +5,22 @@ import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 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.HandlerThread;
import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.network.registration.PayloadRegistrar;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.dummy.entity.BaseMultiblockDummyBlockEntity; 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.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity; import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity; import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities; import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.network.ClientPlayerStateUpdateHandler;
import net.xevianlight.aphelion.network.RocketPayloadHandlers; import net.xevianlight.aphelion.network.RocketPayloadHandlers;
import net.xevianlight.aphelion.network.ServerPayloadHandler; import net.xevianlight.aphelion.network.PartitionPayloadHandler;
import net.xevianlight.aphelion.network.packet.PartitionData; 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.RocketLaunchPayload;
import net.xevianlight.aphelion.network.packet.UpdateGravityTestBlockPacket;
@EventBusSubscriber(modid = Aphelion.MOD_ID) @EventBusSubscriber(modid = Aphelion.MOD_ID)
public class ModBusEvents { public class ModBusEvents {
@@ -40,9 +42,9 @@ public class ModBusEvents {
.executesOn(HandlerThread.MAIN); .executesOn(HandlerThread.MAIN);
registrar.playToClient( registrar.playToClient(
PartitionData.TYPE, PartitionPayload.TYPE,
PartitionData.STREAM_CODEC, PartitionPayload.STREAM_CODEC,
ServerPayloadHandler::handleDataOnMain PartitionPayloadHandler::handleDataOnMain
); );
registrar.playToServer( registrar.playToServer(
@@ -51,5 +53,16 @@ public class ModBusEvents {
RocketPayloadHandlers::handleRocketLaunch RocketPayloadHandlers::handleRocketLaunch
); );
registrar.playToClient(
ClientPlayerStateUpdatePacket.TYPE,
ClientPlayerStateUpdatePacket.STREAM_CODEC,
ClientPlayerStateUpdateHandler::handleDataOnMain
);
registrar.playToServer(
UpdateGravityTestBlockPacket.TYPE,
UpdateGravityTestBlockPacket.STREAM_CODEC,
UpdateGravityTestBlockHandler::handleDataOnMain
);
} }
} }

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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())));
}
}

View File

@@ -2,12 +2,16 @@ package net.xevianlight.aphelion.network;
import net.minecraft.client.Minecraft; 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.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.ClientTickEvent; import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.entites.vehicles.RocketEntity; 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.network.packet.RocketLaunchPayload;
import net.xevianlight.aphelion.client.AphelionClient; 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 // consumeClick makes it fire once per press, not every tick held
if (AphelionClient.ROCKET_LAUNCH_KEY.consumeClick() && mc.player.getVehicle() instanceof RocketEntity rocket) { 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())); 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()));
}
}
}
} }

View File

@@ -3,13 +3,14 @@ package net.xevianlight.aphelion.network;
import net.neoforged.neoforge.network.handling.IPayloadContext; import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.client.PartitionClientState; 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 // 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); PartitionClientState.set(data);
Aphelion.LOGGER.info("Partition packet received! id={}", data.id()); Aphelion.LOGGER.info("Partition packet received! id={}", data.partitionData());
} }
} }

View File

@@ -4,33 +4,21 @@ package net.xevianlight.aphelion.network;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; 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.event.tick.ServerTickEvent;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.core.space.SpacePartitionSavedData; import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
import net.xevianlight.aphelion.network.packet.PartitionData; import net.xevianlight.aphelion.core.saveddata.types.PartitionData;
import net.xevianlight.aphelion.network.packet.PartitionPayload;
import net.xevianlight.aphelion.util.SpacePartitionHelper; import net.xevianlight.aphelion.util.SpacePartitionHelper;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
@EventBusSubscriber(modid = Aphelion.MOD_ID) @EventBusSubscriber(modid = Aphelion.MOD_ID)
public final class PartitionSync { public final class PartitionSync {
// send once right after join (safe: delayed to next server tick) // Stora all packets we send to all players in a map so we can look it up later
private static final Set<UUID> PENDING_JOIN_SEND = new HashSet<>(); private static final java.util.Map<UUID, PartitionPayload> LAST_SENT = new java.util.HashMap<>();
@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<>();
@SubscribeEvent @SubscribeEvent
public static void onServerTick(ServerTickEvent.Post e) { public static void onServerTick(ServerTickEvent.Post e) {
@@ -39,24 +27,29 @@ public final class PartitionSync {
// Aphelion.LOGGER.info("WORKS!!!"); // Aphelion.LOGGER.info("WORKS!!!");
for (ServerPlayer sp : server.getPlayerList().getPlayers()) { 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)) { if (prev == null || !prev.equals(now)) {
PacketDistributor.sendToPlayer(sp, now); PacketDistributor.sendToPlayer(sp, now);
// Store this packet for later
LAST_SENT.put(sp.getUUID(), now); LAST_SENT.put(sp.getUUID(), now);
} }
} }
} }
private static PartitionData computePartitionFor(ServerPlayer sp) { private static PartitionPayload computePartitionFor(ServerPlayer sp) {
// Example: convert player position to partition coords
int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE); int px = (int)Math.floor(sp.getX() / SpacePartitionHelper.SIZE);
int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE); int pz = (int)Math.floor(sp.getZ() / SpacePartitionHelper.SIZE);
var orbit = SpacePartitionSavedData.get(sp.serverLevel()).getOrbitForPartition(px, pz); PartitionData live = SpacePartitionSavedData.get(sp.serverLevel()).getData(px, pz);
String orbitId = (orbit != null) ? orbit.toString() : "aphelion:orbit/default";
return new PartitionData(orbitId); // snapshot so mutations later dont affect cached payloads
PartitionData snapshot = (live == null) ? null : new PartitionData(live);
return new PartitionPayload(snapshot);
} }
} }

View File

@@ -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);
}
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -10,12 +10,16 @@ import net.xevianlight.aphelion.util.registries.ModRegistries;
public record Planet( public record Planet(
ResourceKey<Level> dimension, ResourceKey<Level> dimension,
double orbitDistance, double orbitDistance,
ResourceKey<StarSystem> system ResourceKey<StarSystem> system,
boolean oxygen,
float gravity
) { ) {
public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group( public static final Codec<Planet> CODEC = RecordCodecBuilder.create(inst -> inst.group(
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension), ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(Planet::dimension),
Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance), Codec.DOUBLE.fieldOf("orbit_distance").forGetter(Planet::orbitDistance),
ResourceKey.codec(ModRegistries.STAR_SYSTEM).fieldOf("star_system").forGetter(Planet::system) 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)); ).apply(inst, Planet::new));
} }

View File

@@ -18,7 +18,9 @@ public final class PlanetCache {
public static final Planet DEFAULT = new Planet( public static final Planet DEFAULT = new Planet(
ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")), ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")),
1, 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) { public static void registerPlanets(Map<ResourceLocation, Planet> planets) {

View File

@@ -7,6 +7,6 @@ public record StarSystem(
int temp int temp
) { ) {
public static final Codec<StarSystem> CODEC = RecordCodecBuilder.create(inst -> inst.group( 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)); ).apply(inst, StarSystem::new));
} }

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}

View File

@@ -10,6 +10,8 @@ import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.DeferredRegister;
import net.xevianlight.aphelion.Aphelion; import net.xevianlight.aphelion.Aphelion;
import java.awt.*;
public class ModMenuTypes { public class ModMenuTypes {
public static final DeferredRegister<MenuType<?>> MENUS = public static final DeferredRegister<MenuType<?>> MENUS =
DeferredRegister.create(Registries.MENU, Aphelion.MOD_ID); DeferredRegister.create(Registries.MENU, Aphelion.MOD_ID);
@@ -23,6 +25,9 @@ public class ModMenuTypes {
public static DeferredHolder<MenuType<?>,MenuType<VacuumArcFurnaceMenu>> VACUUM_ARC_FURNACE_MENU = public static DeferredHolder<MenuType<?>,MenuType<VacuumArcFurnaceMenu>> VACUUM_ARC_FURNACE_MENU =
registerMenuType("vacuum_arc_furnace_menu", VacuumArcFurnaceMenu::new); 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, private static <T extends AbstractContainerMenu>DeferredHolder<MenuType<?>, MenuType<T>> registerMenuType(String name,
IContainerFactory<T> factory) { IContainerFactory<T> factory) {
return MENUS.register(name, () -> IMenuTypeExtension.create(factory)); return MENUS.register(name, () -> IMenuTypeExtension.create(factory));

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -17,6 +17,10 @@ public class ModTags {
public static final TagKey<Block> STORAGE_BLOCKS = commonTag("storage_blocks"); 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> 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) { private static TagKey<Block> commonTag(String name) {
return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name)); return BlockTags.create(ResourceLocation.fromNamespaceAndPath("c", name));
} }
@@ -25,7 +29,6 @@ public class ModTags {
public static class Items { public static class Items {
public static final TagKey<Item> TEST_TAG = createTag("test_tag"); public static final TagKey<Item> TEST_TAG = createTag("test_tag");
public static final TagKey<Item> INGOTS = commonTag("ingots"); 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 = commonTag("storage_blocks");
public static final TagKey<Item> STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel"); public static final TagKey<Item> STORAGE_BLOCKS_STEEL = commonTag("storage_blocks/steel");
public static final TagKey<Item> INGOT_ALUMINUM = commonTag("ingots/aluminum"); public static final TagKey<Item> INGOT_ALUMINUM = commonTag("ingots/aluminum");

View File

@@ -2,7 +2,9 @@ package net.xevianlight.aphelion.util;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
@@ -135,4 +137,37 @@ public final class RocketStructure {
return new Extents(minX, minY, minZ, maxX, maxY, maxZ); 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);
}
}
} }

View 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;
}
}
}

View File

@@ -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). # There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0. # The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file. # Note that there are a couple of TOML lists in this file.

View File

@@ -4,6 +4,8 @@
"package": "net.xevianlight.aphelion.mixins", "package": "net.xevianlight.aphelion.mixins",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [ "mixins": [
"common.EntityMixin",
"common.LivingEntityMixin"
], ],
"client": [ "client": [
"common.ClientLevelMixin", "common.ClientLevelMixin",

View File

@@ -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" }
}
}

View File

@@ -3,6 +3,7 @@
"block.aphelion.test_block": "Test Block", "block.aphelion.test_block": "Test Block",
"block.aphelion.electric_arc_furnace": "Electric Arc Furnace", "block.aphelion.electric_arc_furnace": "Electric Arc Furnace",
"block.aphelion.vacuum_arc_furnace_controller": "Vacuum Arc Furnace Controller", "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", "block.aphelion.vaf_dummy_block": "Vacuum Arc Furnace",
"item.aphelion.ingot_steel": "Steel Ingot", "item.aphelion.ingot_steel": "Steel Ingot",
@@ -28,6 +29,10 @@
"tag.item.c.ingots.steel": "Steel Ingots", "tag.item.c.ingots.steel": "Steel Ingots",
"tag.item.c.ingots.aluminum": "Aluminum 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.set": "Set station (%s, %s)'s orbit to %s",
"command.aphelion.station.orbit.get": "Station (%s, %s)'s orbit is assigned to %s", "command.aphelion.station.orbit.get": "Station (%s, %s)'s orbit is assigned to %s",

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -0,0 +1,3 @@
{
"parent": "aphelion:block/launch_pad/1111"
}

View File

@@ -6,5 +6,13 @@
"stream": true "stream": true
} }
] ]
},
"rocket_engine": {
"sounds": [
{
"name": "aphelion:rocket_engine",
"stream": true
}
]
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Some files were not shown because too many files have changed in this diff Show More