diff --git a/build.gradle b/build.gradle index c2e6d7d..6234dd9 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,6 @@ version = "${project.mod_version}+${project.minecraft_version}" group = project.maven_group repositories { - maven { url = "https://server.bbkr.space/artifactory/libs-release" } - maven { url = "https://storage.googleapis.com/devan-maven/" } maven { url "https://api.modrinth.com/maven" } } diff --git a/gradle.properties b/gradle.properties index a2891f9..57c8bb2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ loader_version=0.14.21 #Fabric api fabric_version=0.84.0+1.20.1 -mod_version = 1.0.0-beta.1 +mod_version = 1.0.0-beta.2 maven_group = io.github.foundationgames archives_base_name = phonos diff --git a/src/main/java/io/github/foundationgames/phonos/Phonos.java b/src/main/java/io/github/foundationgames/phonos/Phonos.java index fc3854c..970c52b 100644 --- a/src/main/java/io/github/foundationgames/phonos/Phonos.java +++ b/src/main/java/io/github/foundationgames/phonos/Phonos.java @@ -4,6 +4,8 @@ import io.github.foundationgames.phonos.item.ItemGroupQueue; import io.github.foundationgames.phonos.item.PhonosItems; import io.github.foundationgames.phonos.network.PayloadPackets; +import io.github.foundationgames.phonos.radio.RadioDevice; +import io.github.foundationgames.phonos.radio.RadioStorage; import io.github.foundationgames.phonos.sound.SoundStorage; import io.github.foundationgames.phonos.sound.emitter.SoundEmitter; import io.github.foundationgames.phonos.sound.emitter.SoundEmitterStorage; @@ -41,7 +43,8 @@ public void onInitialize() { SoundDataTypes.init(); InputPlugPoint.init(); - ServerLifecycleEvents.SERVER_STARTED.register(e -> { + ServerLifecycleEvents.SERVER_STARTING.register(e -> { + RadioStorage.serverReset(); SoundStorage.serverReset(); SoundEmitterStorage.serverReset(); }); @@ -51,12 +54,20 @@ public void onInitialize() { if (be instanceof SoundEmitter p) { SoundEmitterStorage.getInstance(world).addEmitter(p); } + if (be instanceof RadioDevice.Receiver rec) { + rec.setAndUpdateChannel(rec.getChannel()); + } }); ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((be, world) -> { if (be instanceof SoundEmitter p) { SoundEmitterStorage.getInstance(world).removeEmitter(p); } + if (be instanceof RadioDevice.Receiver rec) { + rec.removeReceiver(); + } }); + + RadioStorage.init(); } public static Identifier id(String path) { diff --git a/src/main/java/io/github/foundationgames/phonos/PhonosClient.java b/src/main/java/io/github/foundationgames/phonos/PhonosClient.java index 4002a51..e3a13bf 100644 --- a/src/main/java/io/github/foundationgames/phonos/PhonosClient.java +++ b/src/main/java/io/github/foundationgames/phonos/PhonosClient.java @@ -3,15 +3,18 @@ import io.github.foundationgames.jsonem.JsonEM; import io.github.foundationgames.phonos.block.PhonosBlocks; import io.github.foundationgames.phonos.client.render.block.CableOutputBlockEntityRenderer; +import io.github.foundationgames.phonos.client.render.block.RadioLoudspeakerBlockEntityRenderer; +import io.github.foundationgames.phonos.client.render.block.RadioTransceiverBlockEntityRenderer; import io.github.foundationgames.phonos.item.AudioCableItem; import io.github.foundationgames.phonos.item.PhonosItems; import io.github.foundationgames.phonos.network.ClientPayloadPackets; +import io.github.foundationgames.phonos.radio.RadioDevice; +import io.github.foundationgames.phonos.radio.RadioStorage; import io.github.foundationgames.phonos.sound.ClientSoundStorage; import io.github.foundationgames.phonos.sound.SoundStorage; import io.github.foundationgames.phonos.sound.emitter.SoundEmitter; import io.github.foundationgames.phonos.sound.emitter.SoundEmitterStorage; import io.github.foundationgames.phonos.util.PhonosUtil; -import io.github.foundationgames.phonos.world.sound.data.SoundDataTypes; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; @@ -35,10 +38,14 @@ public void onInitializeClient() { JsonEM.registerModelLayer(AUDIO_CABLE_END_LAYER); BlockRenderLayerMap.INSTANCE.putBlock(PhonosBlocks.ELECTRONIC_NOTE_BLOCK, RenderLayer.getCutout()); + BlockRenderLayerMap.INSTANCE.putBlock(PhonosBlocks.RADIO_TRANSCEIVER, RenderLayer.getCutout()); + BlockRenderLayerMap.INSTANCE.putBlock(PhonosBlocks.RADIO_LOUDSPEAKER, RenderLayer.getCutout()); BlockEntityRendererFactories.register(PhonosBlocks.ELECTRONIC_NOTE_BLOCK_ENTITY, CableOutputBlockEntityRenderer::new); BlockEntityRendererFactories.register(PhonosBlocks.ELECTRONIC_JUKEBOX_ENTITY, CableOutputBlockEntityRenderer::new); BlockEntityRendererFactories.register(PhonosBlocks.CONNECTION_HUB_ENTITY, CableOutputBlockEntityRenderer::new); + BlockEntityRendererFactories.register(PhonosBlocks.RADIO_TRANSCEIVER_ENTITY, RadioTransceiverBlockEntityRenderer::new); + BlockEntityRendererFactories.register(PhonosBlocks.RADIO_LOUDSPEAKER_ENTITY, RadioLoudspeakerBlockEntityRenderer::new); ColorProviderRegistry.BLOCK.register((state, world, pos, tintIndex) -> world != null && pos != null && state != null ? @@ -55,6 +62,7 @@ public void onInitializeClient() { ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> { if (entity == MinecraftClient.getInstance().player) { + RadioStorage.clientReset(); SoundStorage.clientReset(); SoundEmitterStorage.clientReset(); } @@ -66,11 +74,17 @@ public void onInitializeClient() { if (be instanceof SoundEmitter p) { SoundEmitterStorage.getInstance(world).addEmitter(p); } + if (be instanceof RadioDevice.Receiver rec) { + rec.setAndUpdateChannel(rec.getChannel()); + } }); ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((be, world) -> { if (be instanceof SoundEmitter p) { SoundEmitterStorage.getInstance(world).removeEmitter(p); } + if (be instanceof RadioDevice.Receiver rec) { + rec.removeReceiver(); + } }); //ScreenRegistry.register(Phonos.RADIO_JUKEBOX_HANDLER, (gui, inventory, title) -> new RadioJukeboxScreen(gui, inventory.player)); diff --git a/src/main/java/io/github/foundationgames/phonos/block/AbstractLoudspeakerBlock.java b/src/main/java/io/github/foundationgames/phonos/block/AbstractLoudspeakerBlock.java new file mode 100644 index 0000000..ed0b811 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/block/AbstractLoudspeakerBlock.java @@ -0,0 +1,67 @@ +package io.github.foundationgames.phonos.block; + +import io.github.foundationgames.phonos.world.sound.block.SoundDataHandler; +import io.github.foundationgames.phonos.world.sound.data.NoteBlockSoundData; +import io.github.foundationgames.phonos.world.sound.data.SoundData; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.HorizontalFacingBlock; +import net.minecraft.client.MinecraftClient; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.state.StateManager; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class AbstractLoudspeakerBlock extends HorizontalFacingBlock implements SoundDataHandler { + public AbstractLoudspeakerBlock(Settings settings) { + super(settings); + + setDefaultState(getDefaultState().with(FACING, Direction.NORTH)); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + builder.add(FACING); + } + + @Nullable + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + return getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + } + + @Override + public void receiveSound(BlockState state, World world, BlockPos pos, SoundData sound) { + if (!world.isClient()) { + return; + } + + if (MinecraftClient.getInstance().player.getPos().squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ()) > 5000) { + return; + } + + if (sound instanceof NoteBlockSoundData noteData) { + double note = noteData.note / 24D; + if (!world.getBlockState(pos.up()).isSideSolidFullSquare(world, pos.up(), Direction.DOWN)) { + world.addParticle(ParticleTypes.NOTE, + pos.getX() + 0.5, pos.getY() + 1.1, pos.getZ() + 0.5, + note, 0, 0); + } else { + var facing = state.get(FACING); + for (var dir : Direction.Type.HORIZONTAL) if (dir != facing.getOpposite()) { + if (!world.getBlockState(pos.offset(dir)).isSideSolidFullSquare(world, pos.offset(dir), dir.getOpposite())) { + world.addParticle(ParticleTypes.NOTE, + pos.getX() + 0.5 + dir.getVector().getX() * 0.6, + pos.getY() + 0.35, + pos.getZ() + 0.5 + dir.getVector().getZ() * 0.6, + note, 0, 0); + } + } + } + } + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/block/ElectronicJukeboxBlock.java b/src/main/java/io/github/foundationgames/phonos/block/ElectronicJukeboxBlock.java index 8077512..b817c9c 100644 --- a/src/main/java/io/github/foundationgames/phonos/block/ElectronicJukeboxBlock.java +++ b/src/main/java/io/github/foundationgames/phonos/block/ElectronicJukeboxBlock.java @@ -52,18 +52,18 @@ public ActionResult useMusicDisc(World world, BlockPos pos, BlockState state, It @Override public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { var side = hit.getSide(); - if (side == Direction.UP) { - var stack = player.getStackInHand(hand); - if (!state.get(HAS_RECORD) && stack.getItem() instanceof MusicDiscItem) { - return this.useMusicDisc(world, pos, state, stack, player); - } - return super.onUse(state, world, pos, player, hand, hit); - } if (side == Direction.DOWN) { return ActionResult.PASS; } + var stack = player.getStackInHand(hand); + if (!state.get(HAS_RECORD) && stack.getItem() instanceof MusicDiscItem) { + return this.useMusicDisc(world, pos, state, stack, player); + } else if (side == Direction.UP) { + return super.onUse(state, world, pos, player, hand, hit); + } + if (player.canModifyBlocks()) { if (!world.isClient() && world.getBlockEntity(pos) instanceof ElectronicJukeboxBlockEntity be) { if (!PhonosUtil.holdingAudioCable(player) && be.outputs.tryRemoveConnection(world, hit, !player.isCreative())) { diff --git a/src/main/java/io/github/foundationgames/phonos/block/ElectronicNoteBlock.java b/src/main/java/io/github/foundationgames/phonos/block/ElectronicNoteBlock.java index 1cbe98d..687a212 100644 --- a/src/main/java/io/github/foundationgames/phonos/block/ElectronicNoteBlock.java +++ b/src/main/java/io/github/foundationgames/phonos/block/ElectronicNoteBlock.java @@ -1,6 +1,5 @@ package io.github.foundationgames.phonos.block; -import io.github.foundationgames.phonos.block.entity.ElectronicJukeboxBlockEntity; import io.github.foundationgames.phonos.block.entity.ElectronicNoteBlockEntity; import io.github.foundationgames.phonos.sound.SoundStorage; import io.github.foundationgames.phonos.sound.emitter.SoundEmitterTree; diff --git a/src/main/java/io/github/foundationgames/phonos/block/LoudspeakerBlock.java b/src/main/java/io/github/foundationgames/phonos/block/LoudspeakerBlock.java index 570bc58..38ab117 100644 --- a/src/main/java/io/github/foundationgames/phonos/block/LoudspeakerBlock.java +++ b/src/main/java/io/github/foundationgames/phonos/block/LoudspeakerBlock.java @@ -3,17 +3,10 @@ import io.github.foundationgames.phonos.util.PhonosUtil; import io.github.foundationgames.phonos.world.sound.block.BlockConnectionLayout; import io.github.foundationgames.phonos.world.sound.block.InputBlock; -import io.github.foundationgames.phonos.world.sound.block.SoundDataHandler; -import io.github.foundationgames.phonos.world.sound.data.NoteBlockSoundData; -import io.github.foundationgames.phonos.world.sound.data.SoundData; import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.block.HorizontalFacingBlock; -import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemUsageContext; -import net.minecraft.particle.ParticleTypes; import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; import net.minecraft.util.ActionResult; @@ -23,9 +16,8 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; -import org.jetbrains.annotations.Nullable; -public class LoudspeakerBlock extends HorizontalFacingBlock implements SoundDataHandler, InputBlock { +public class LoudspeakerBlock extends AbstractLoudspeakerBlock implements InputBlock { public static final BooleanProperty[] INPUTS = BlockProperties.pluggableInputs(4); public final BlockConnectionLayout inputLayout = new BlockConnectionLayout() @@ -37,13 +29,13 @@ public class LoudspeakerBlock extends HorizontalFacingBlock implements SoundData public LoudspeakerBlock(Settings settings) { super(settings); - setDefaultState(BlockProperties.withAll(getDefaultState(), INPUTS, false).with(FACING, Direction.NORTH)); + setDefaultState(BlockProperties.withAll(getDefaultState(), INPUTS, false)); } @Override protected void appendProperties(StateManager.Builder builder) { super.appendProperties(builder); - builder.add(FACING); + builder.add(INPUTS); } @@ -60,12 +52,6 @@ public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEnt return ActionResult.success(hit.getSide().equals(state.get(FACING).getOpposite())); } - @Nullable - @Override - public BlockState getPlacementState(ItemPlacementContext ctx) { - return getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); - } - @Override public boolean canInputConnect(ItemUsageContext ctx) { var world = ctx.getWorld(); @@ -92,37 +78,6 @@ public Direction getRotation(BlockState state) { return state.get(FACING); } - @Override - public void receiveSound(BlockState state, World world, BlockPos pos, SoundData sound) { - if (!world.isClient()) { - return; - } - - if (MinecraftClient.getInstance().player.getPos().squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ()) > 5000) { - return; - } - - if (sound instanceof NoteBlockSoundData noteData) { - double note = noteData.note / 24D; - if (!world.getBlockState(pos.up()).isSolidBlock(world, pos.up())) { - world.addParticle(ParticleTypes.NOTE, - pos.getX() + 0.5, pos.getY() + 1.1, pos.getZ() + 0.5, - note, 0, 0); - } else { - var facing = state.get(FACING); - for (var dir : Direction.Type.HORIZONTAL) if (dir != facing.getOpposite()) { - if (!world.getBlockState(pos.offset(dir)).isSolidBlock(world, pos.offset(dir))) { - world.addParticle(ParticleTypes.NOTE, - pos.getX() + 0.5 + dir.getVector().getX() * 0.6, - pos.getY() + 0.35, - pos.getZ() + 0.5 + dir.getVector().getZ() * 0.6, - note, 0, 0); - } - } - } - } - } - @Override public boolean isInputPluggedIn(int inputIndex, BlockState state, World world, BlockPos pos) { inputIndex = MathHelper.clamp(inputIndex, 0, 3); diff --git a/src/main/java/io/github/foundationgames/phonos/block/PhonosBlocks.java b/src/main/java/io/github/foundationgames/phonos/block/PhonosBlocks.java index 02f5cbd..03b8d1f 100644 --- a/src/main/java/io/github/foundationgames/phonos/block/PhonosBlocks.java +++ b/src/main/java/io/github/foundationgames/phonos/block/PhonosBlocks.java @@ -1,9 +1,7 @@ package io.github.foundationgames.phonos.block; import io.github.foundationgames.phonos.Phonos; -import io.github.foundationgames.phonos.block.entity.ConnectionHubBlockEntity; -import io.github.foundationgames.phonos.block.entity.ElectronicJukeboxBlockEntity; -import io.github.foundationgames.phonos.block.entity.ElectronicNoteBlockEntity; +import io.github.foundationgames.phonos.block.entity.*; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.minecraft.block.Block; import net.minecraft.block.Blocks; @@ -18,6 +16,8 @@ public class PhonosBlocks { public static final Block ELECTRONIC_NOTE_BLOCK = register(new ElectronicNoteBlock(FabricBlockSettings.copy(Blocks.NOTE_BLOCK)), "electronic_note_block"); public static final Block ELECTRONIC_JUKEBOX = register(new ElectronicJukeboxBlock(FabricBlockSettings.copy(Blocks.JUKEBOX)), "electronic_jukebox"); public static final Block CONNECTION_HUB = register(new ConnectionHubBlock(FabricBlockSettings.copy(Blocks.OAK_PLANKS)), "connection_hub"); + public static final Block RADIO_TRANSCEIVER = register(new RadioTransceiverBlock(FabricBlockSettings.copy(Blocks.OAK_SLAB)), "radio_transceiver"); + public static final Block RADIO_LOUDSPEAKER = register(new RadioLoudspeakerBlock(FabricBlockSettings.copy(Blocks.NOTE_BLOCK)), "radio_loudspeaker"); public static BlockEntityType ELECTRONIC_NOTE_BLOCK_ENTITY = Registry.register( Registries.BLOCK_ENTITY_TYPE, Phonos.id("electronic_note_block"), @@ -28,6 +28,12 @@ public class PhonosBlocks { public static BlockEntityType CONNECTION_HUB_ENTITY = Registry.register( Registries.BLOCK_ENTITY_TYPE, Phonos.id("connection_hub"), BlockEntityType.Builder.create(ConnectionHubBlockEntity::new, CONNECTION_HUB).build(null)); + public static BlockEntityType RADIO_TRANSCEIVER_ENTITY = Registry.register( + Registries.BLOCK_ENTITY_TYPE, Phonos.id("radio_transceiver"), + BlockEntityType.Builder.create(RadioTransceiverBlockEntity::new, RADIO_TRANSCEIVER).build(null)); + public static BlockEntityType RADIO_LOUDSPEAKER_ENTITY = Registry.register( + Registries.BLOCK_ENTITY_TYPE, Phonos.id("radio_loudspeaker"), + BlockEntityType.Builder.create(RadioLoudspeakerBlockEntity::new, RADIO_LOUDSPEAKER).build(null)); private static Block register(Block block, String name) { var item = Registry.register(Registries.ITEM, Phonos.id(name), new BlockItem(block, new Item.Settings())); diff --git a/src/main/java/io/github/foundationgames/phonos/block/RadioLoudspeakerBlock.java b/src/main/java/io/github/foundationgames/phonos/block/RadioLoudspeakerBlock.java new file mode 100644 index 0000000..1fc0ada --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/block/RadioLoudspeakerBlock.java @@ -0,0 +1,56 @@ +package io.github.foundationgames.phonos.block; + +import io.github.foundationgames.phonos.block.entity.RadioLoudspeakerBlockEntity; +import io.github.foundationgames.phonos.util.PhonosUtil; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class RadioLoudspeakerBlock extends AbstractLoudspeakerBlock implements BlockEntityProvider { + public RadioLoudspeakerBlock(Settings settings) { + super(settings); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + var side = hit.getSide(); + var facing = state.get(FACING); + + if (side == facing.getOpposite()) { + if (!world.isClient()) { + int inc = player.isSneaking() ? -1 : 1; + if (world.getBlockEntity(pos) instanceof RadioLoudspeakerBlockEntity be) { + be.setAndUpdateChannel(be.getChannel() + inc); + be.markDirty(); + } + + return ActionResult.CONSUME; + } + + return ActionResult.SUCCESS; + } + + return super.onUse(state, world, pos, player, hand, hit); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new RadioLoudspeakerBlockEntity(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return PhonosUtil.blockEntityTicker(type, PhonosBlocks.RADIO_LOUDSPEAKER_ENTITY); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/block/RadioTransceiverBlock.java b/src/main/java/io/github/foundationgames/phonos/block/RadioTransceiverBlock.java new file mode 100644 index 0000000..89ebb2a --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/block/RadioTransceiverBlock.java @@ -0,0 +1,171 @@ +package io.github.foundationgames.phonos.block; + +import io.github.foundationgames.phonos.block.entity.RadioTransceiverBlockEntity; +import io.github.foundationgames.phonos.util.PhonosUtil; +import io.github.foundationgames.phonos.world.sound.block.BlockConnectionLayout; +import io.github.foundationgames.phonos.world.sound.block.InputBlock; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.state.StateManager; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class RadioTransceiverBlock extends HorizontalFacingBlock implements BlockEntityProvider, InputBlock { + private static final VoxelShape SHAPE = createCuboidShape(0, 0, 0, 16, 7, 16); + + public final BlockConnectionLayout inputLayout = new BlockConnectionLayout() + .addPoint(-4.5, -4.5, -8, Direction.NORTH) + .addPoint(4.5, -4.5, -8, Direction.NORTH); + + public RadioTransceiverBlock(Settings settings) { + super(settings); + setDefaultState(getDefaultState().with(FACING, Direction.NORTH)); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + var side = hit.getSide(); + var facing = state.get(FACING); + + if (side == Direction.DOWN) { + return ActionResult.PASS; + } + + if (side == Direction.UP) { + if (!world.isClient()) { + int inc = player.isSneaking() ? -1 : 1; + if (world.getBlockEntity(pos) instanceof RadioTransceiverBlockEntity be) { + be.setAndUpdateChannel(be.getChannel() + inc); + be.markDirty(); + } + + return ActionResult.CONSUME; + } + + return ActionResult.SUCCESS; + } + + if (player.canModifyBlocks()) { + if (!world.isClient() && world.getBlockEntity(pos) instanceof RadioTransceiverBlockEntity be) { + if (PhonosUtil.holdingAudioCable(player)) { + return ActionResult.PASS; + } + + if (side != facing) { + if (be.outputs.tryRemoveConnection(world, hit, !player.isCreative())) { + be.sync(); + return ActionResult.SUCCESS; + } + } else { + return tryRemoveConnection(state, world, pos, hit); + } + } + } + + return ActionResult.success(side == facing); + } + + @Override + public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { + if (!newState.isOf(this) && world.getBlockEntity(pos) instanceof RadioTransceiverBlockEntity be) { + be.onDestroyed(); + } + + super.onStateReplaced(state, world, pos, newState, moved); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return SHAPE; + } + + @Override + public Direction getRotation(BlockState state) { + return state.get(FACING); + } + + @Nullable + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + return getDefaultState().with(FACING, ctx.getHorizontalPlayerFacing().getOpposite()); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + + builder.add(FACING); + } + + @Override + public boolean canInputConnect(ItemUsageContext ctx) { + var world = ctx.getWorld(); + var pos = ctx.getBlockPos(); + var state = world.getBlockState(pos); + var facing = state.get(FACING); + var side = ctx.getSide(); + + if (side == facing) { + int index = this.getInputLayout().getClosestIndexClicked(ctx.getHitPos(), pos, getRotation(state)); + + return !this.isInputPluggedIn(index, state, world, pos); + } + + return false; + } + + @Override + public boolean playsSound(World world, BlockPos pos) { + return false; + } + + @Override + public boolean isInputPluggedIn(int inputIndex, BlockState state, World world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof RadioTransceiverBlockEntity be) { + inputIndex = MathHelper.clamp(inputIndex, 0, be.inputs.length - 1); + + return be.inputs[inputIndex]; + } + + return false; + } + + @Override + public void setInputPluggedIn(int inputIndex, boolean pluggedIn, BlockState state, World world, BlockPos pos) { + if (world.getBlockEntity(pos) instanceof RadioTransceiverBlockEntity be) { + inputIndex = MathHelper.clamp(inputIndex, 0, be.inputs.length - 1); + be.inputs[inputIndex] = pluggedIn; + be.sync(); + } + } + + @Override + public BlockConnectionLayout getInputLayout() { + return inputLayout; + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new RadioTransceiverBlockEntity(pos, state); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return PhonosUtil.blockEntityTicker(type, PhonosBlocks.RADIO_TRANSCEIVER_ENTITY); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/block/entity/ElectronicJukeboxBlockEntity.java b/src/main/java/io/github/foundationgames/phonos/block/entity/ElectronicJukeboxBlockEntity.java index 885b80e..1d06e2f 100644 --- a/src/main/java/io/github/foundationgames/phonos/block/entity/ElectronicJukeboxBlockEntity.java +++ b/src/main/java/io/github/foundationgames/phonos/block/entity/ElectronicJukeboxBlockEntity.java @@ -1,6 +1,7 @@ package io.github.foundationgames.phonos.block.entity; import io.github.foundationgames.phonos.block.PhonosBlocks; +import io.github.foundationgames.phonos.network.PayloadPackets; import io.github.foundationgames.phonos.sound.SoundStorage; import io.github.foundationgames.phonos.sound.emitter.SoundEmitterTree; import io.github.foundationgames.phonos.sound.emitter.SoundSource; @@ -20,6 +21,7 @@ import net.minecraft.network.listener.ClientPlayPacketListener; import net.minecraft.network.packet.Packet; import net.minecraft.registry.Registries; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.DyeColor; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -44,6 +46,8 @@ public class ElectronicJukeboxBlockEntity extends JukeboxBlockEntity implements private @Nullable NbtCompound pendingNbt = null; private final long emitterId; + private @Nullable SoundEmitterTree playingSound = null; + public ElectronicJukeboxBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(pos, state); this.type = type; @@ -68,9 +72,11 @@ public void startPlaying() { this.world.updateNeighborsAlways(this.getPos(), this.getCachedState().getBlock()); if (this.getStack().getItem() instanceof MusicDiscItem disc && !world.isClient()) { + this.playingSound = new SoundEmitterTree(this.emitterId); + SoundStorage.getInstance(world).play(world, SoundEventSoundData.create( emitterId, Registries.SOUND_EVENT.getEntry(disc.getSound()), 2, 1), - new SoundEmitterTree(this.emitterId)); + this.playingSound); sync(); } @@ -84,6 +90,8 @@ protected void stopPlaying() { this.world.updateNeighborsAlways(this.getPos(), this.getCachedState().getBlock()); if (!world.isClient()) { + this.playingSound = null; + SoundStorage.getInstance(world).stop(world, emitterId); sync(); } @@ -100,6 +108,14 @@ public void tick(World world, BlockPos pos, BlockState state) { if (!world.isClient()) { super.tick(world, pos, state); + if (this.playingSound != null) { + var delta = this.playingSound.updateServer(world); + + if (delta.hasChanges() && world instanceof ServerWorld sWorld) for (var player : sWorld.getPlayers()) { + PayloadPackets.sendSoundUpdate(player, delta); + } + } + if (this.outputs.purge(conn -> this.outputs.dropConnectionItem(world, conn, true))) { sync(); } diff --git a/src/main/java/io/github/foundationgames/phonos/block/entity/RadioLoudspeakerBlockEntity.java b/src/main/java/io/github/foundationgames/phonos/block/entity/RadioLoudspeakerBlockEntity.java new file mode 100644 index 0000000..7c67dd0 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/block/entity/RadioLoudspeakerBlockEntity.java @@ -0,0 +1,130 @@ +package io.github.foundationgames.phonos.block.entity; + +import io.github.foundationgames.phonos.block.PhonosBlocks; +import io.github.foundationgames.phonos.radio.RadioDevice; +import io.github.foundationgames.phonos.radio.RadioStorage; +import io.github.foundationgames.phonos.sound.emitter.SoundSource; +import io.github.foundationgames.phonos.world.sound.block.SoundDataHandler; +import io.github.foundationgames.phonos.world.sound.data.SoundData; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class RadioLoudspeakerBlockEntity extends BlockEntity implements Syncing, Ticking, RadioDevice.Receiver, SoundSource { + private int channel = 0; + private boolean needsAdd = false; + + public RadioLoudspeakerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public RadioLoudspeakerBlockEntity(BlockPos pos, BlockState state) { + this(PhonosBlocks.RADIO_LOUDSPEAKER_ENTITY, pos, state); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + + if (this.world == null) { + this.needsAdd = true; + this.channel = nbt.getInt("channel"); + } else { + this.setAndUpdateChannel(nbt.getInt("channel")); + } + } + + @Override + protected void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + + nbt.putInt("channel", this.getChannel()); + } + + @Override + public NbtCompound toInitialChunkDataNbt() { + NbtCompound nbt = new NbtCompound(); + this.writeNbt(nbt); + return nbt; + } + + @Nullable + @Override + public Packet toUpdatePacket() { + return this.getPacket(); + } + + @Override + public int getChannel() { + return channel; + } + + @Override + public void setAndUpdateChannel(int channel) { + channel = Math.floorMod(channel, RadioStorage.CHANNEL_COUNT); + + var radio = RadioStorage.getInstance(this.world); + + radio.removeReceivingSource(this.channel, this); + this.channel = channel; + radio.addReceivingSource(channel, this); + + sync(); + } + + @Override + public void addReceiver() { + setAndUpdateChannel(getChannel()); + } + + @Override + public void removeReceiver() { + var radio = RadioStorage.getInstance(this.world); + radio.removeReceivingSource(this.getChannel(), this); + } + + @Override + public double x() { + return this.getPos().getX() + 0.5; + } + + @Override + public double y() { + return this.getPos().getY() + 0.5; + } + + @Override + public double z() { + return this.getPos().getZ() + 0.5; + } + + @Override + public void onSoundPlayed(World world, SoundData sound) { + var pos = this.getPos(); + var state = world.getBlockState(pos); + + if (state.getBlock() instanceof SoundDataHandler block) { + block.receiveSound(state, world, pos, sound); + } + } + + @Override + public void tick(World world, BlockPos pos, BlockState state) { + if (this.needsAdd) { + this.addReceiver(); + this.needsAdd = false; + } + } + + public Direction getRotation() { + return this.getCachedState().get(Properties.HORIZONTAL_FACING); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/block/entity/RadioTransceiverBlockEntity.java b/src/main/java/io/github/foundationgames/phonos/block/entity/RadioTransceiverBlockEntity.java new file mode 100644 index 0000000..e36fab3 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/block/entity/RadioTransceiverBlockEntity.java @@ -0,0 +1,144 @@ +package io.github.foundationgames.phonos.block.entity; + +import io.github.foundationgames.phonos.block.PhonosBlocks; +import io.github.foundationgames.phonos.block.RadioTransceiverBlock; +import io.github.foundationgames.phonos.radio.RadioDevice; +import io.github.foundationgames.phonos.radio.RadioStorage; +import io.github.foundationgames.phonos.world.sound.InputPlugPoint; +import io.github.foundationgames.phonos.world.sound.block.BlockConnectionLayout; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.state.property.Properties; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.function.LongConsumer; + +public class RadioTransceiverBlockEntity extends AbstractConnectionHubBlockEntity implements RadioDevice.Transmitter, RadioDevice.Receiver { + public static final BlockConnectionLayout OUTPUT_LAYOUT = new BlockConnectionLayout() + .addPoint(-8, -5, 0, Direction.WEST) + .addPoint(8, -5, 0, Direction.EAST) + .addPoint(0, -5, 8, Direction.SOUTH); + + private int channel = 0; + private boolean needsAdd = false; + + public RadioTransceiverBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, OUTPUT_LAYOUT, new boolean[2]); + } + + public RadioTransceiverBlockEntity(BlockPos pos, BlockState state) { + this(PhonosBlocks.RADIO_TRANSCEIVER_ENTITY, pos, state); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + + if (this.world == null) { + this.needsAdd = true; + this.channel = nbt.getInt("channel"); + } else { + this.setAndUpdateChannel(nbt.getInt("channel")); + } + } + + @Override + protected void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + + nbt.putInt("channel", this.getChannel()); + } + + @Override + public void forEachChild(LongConsumer action) { + RadioDevice.Transmitter.super.forEachChild(action); + + super.forEachChild(action); + } + + @Override + public int getChannel() { + return channel; + } + + @Override + public boolean canConnect(ItemUsageContext ctx) { + var side = ctx.getSide(); + var facing = getRotation(); + + if (side != Direction.UP && side != Direction.DOWN && side != getCachedState().get(Properties.HORIZONTAL_FACING)) { + return !this.outputs.isOutputPluggedIn(OUTPUT_LAYOUT.getClosestIndexClicked(ctx.getHitPos(), this.getPos(), facing)); + } + + return false; + } + + @Override + public boolean addConnection(Vec3d hitPos, @Nullable DyeColor color, InputPlugPoint destInput, ItemStack cable) { + int index = OUTPUT_LAYOUT.getClosestIndexClicked(hitPos, this.getPos(), getRotation()); + + if (this.outputs.tryPlugOutputIn(index, color, destInput, cable)) { + this.markDirty(); + this.sync(); + return true; + } + + return false; + } + + @Override + public boolean forwards() { + return true; + } + + @Override + public Direction getRotation() { + if (this.getCachedState().getBlock() instanceof RadioTransceiverBlock block) { + return block.getRotation(this.getCachedState()); + } + + return Direction.NORTH; + } + + @Override + public void setAndUpdateChannel(int channel) { + channel = Math.floorMod(channel, RadioStorage.CHANNEL_COUNT); + + var radio = RadioStorage.getInstance(this.world); + + radio.removeReceivingEmitter(this.channel, this.emitterId()); + this.channel = channel; + radio.addReceivingEmitter(channel, this); + + sync(); + } + + @Override + public void addReceiver() { + setAndUpdateChannel(getChannel()); + } + + @Override + public void removeReceiver() { + var radio = RadioStorage.getInstance(this.world); + radio.removeReceivingEmitter(this.getChannel(), this.emitterId()); + } + + @Override + public void tick(World world, BlockPos pos, BlockState state) { + super.tick(world, pos, state); + + if (this.needsAdd) { + this.addReceiver(); + this.needsAdd = false; + } + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioLoudspeakerBlockEntityRenderer.java b/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioLoudspeakerBlockEntityRenderer.java new file mode 100644 index 0000000..4ad04a8 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioLoudspeakerBlockEntityRenderer.java @@ -0,0 +1,52 @@ +package io.github.foundationgames.phonos.client.render.block; + +import io.github.foundationgames.phonos.block.entity.RadioLoudspeakerBlockEntity; +import io.github.foundationgames.phonos.radio.RadioStorage; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.block.entity.BlockEntityRenderer; +import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.math.RotationAxis; + +public class RadioLoudspeakerBlockEntityRenderer implements BlockEntityRenderer { + public static final Text[] CHANNEL_TO_TEXT = new Text[RadioStorage.CHANNEL_COUNT]; + public static int TEXT_COLOR = 0xFF2A2A; + public static int OUTLINE_COLOR = 0x4F0000; + + private final TextRenderer font; + + public RadioLoudspeakerBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) { + this.font = ctx.getTextRenderer(); + } + + @Override + public void render(RadioLoudspeakerBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { + matrices.push(); + + matrices.translate(0.5, 0.5, 0.5); + matrices.multiply(RotationAxis.NEGATIVE_Y.rotationDegrees(entity.getRotation().asRotation())); + matrices.translate(0, 0, -0.501); + + matrices.scale(0.0268f, 0.0268f, 0.0268f); + matrices.multiply(RotationAxis.POSITIVE_Z.rotation((float) Math.PI)); + matrices.translate(0, 2.25, 0); + + var text = RadioLoudspeakerBlockEntityRenderer.getTextForChannel(entity.getChannel()).asOrderedText(); + + this.font.drawWithOutline(text, -this.font.getWidth(text) * 0.5f, 0, + RadioLoudspeakerBlockEntityRenderer.TEXT_COLOR, RadioLoudspeakerBlockEntityRenderer.OUTLINE_COLOR, + matrices.peek().getPositionMatrix(), vertexConsumers, 15728880); + + matrices.pop(); + } + + public static Text getTextForChannel(int channel) { + if (CHANNEL_TO_TEXT[channel] == null) { + CHANNEL_TO_TEXT[channel] = Text.literal(Integer.toString(channel)); + } + + return CHANNEL_TO_TEXT[channel]; + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioTransceiverBlockEntityRenderer.java b/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioTransceiverBlockEntityRenderer.java new file mode 100644 index 0000000..29318f8 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/client/render/block/RadioTransceiverBlockEntityRenderer.java @@ -0,0 +1,41 @@ +package io.github.foundationgames.phonos.client.render.block; + +import io.github.foundationgames.phonos.block.entity.RadioTransceiverBlockEntity; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.block.entity.BlockEntityRendererFactory; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.RotationAxis; + +public class RadioTransceiverBlockEntityRenderer extends CableOutputBlockEntityRenderer { + private final TextRenderer font; + + public RadioTransceiverBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) { + super(ctx); + + this.font = ctx.getTextRenderer(); + } + + @Override + public void render(RadioTransceiverBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) { + super.render(entity, tickDelta, matrices, vertexConsumers, light, overlay); + + matrices.push(); + + matrices.translate(0.5, 0.4378, 0.5); + + matrices.scale(0.0268f, 0.0268f, 0.0268f); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(entity.getRotation().asRotation())); + + matrices.translate(0, 4.75, 0); + + var text = RadioLoudspeakerBlockEntityRenderer.getTextForChannel(entity.getChannel()).asOrderedText(); + + this.font.drawWithOutline(text, -this.font.getWidth(text) * 0.5f, 0, + RadioLoudspeakerBlockEntityRenderer.TEXT_COLOR, RadioLoudspeakerBlockEntityRenderer.OUTLINE_COLOR, + matrices.peek().getPositionMatrix(), vertexConsumers, 15728880); + + matrices.pop(); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/network/ClientPayloadPackets.java b/src/main/java/io/github/foundationgames/phonos/network/ClientPayloadPackets.java index a2f5c77..b1de784 100644 --- a/src/main/java/io/github/foundationgames/phonos/network/ClientPayloadPackets.java +++ b/src/main/java/io/github/foundationgames/phonos/network/ClientPayloadPackets.java @@ -23,5 +23,11 @@ public static void initClient() { client.execute(() -> SoundStorage.getInstance(client.world).stop(client.world, id)); }); + + ClientPlayNetworking.registerGlobalReceiver(Phonos.id("sound_update"), (client, handler, buf, responseSender) -> { + SoundEmitterTree.Delta delta = SoundEmitterTree.Delta.fromPacket(buf); + + client.execute(() -> SoundStorage.getInstance(client.world).update(delta)); + }); } } diff --git a/src/main/java/io/github/foundationgames/phonos/network/PayloadPackets.java b/src/main/java/io/github/foundationgames/phonos/network/PayloadPackets.java index 25bd849..06decf6 100644 --- a/src/main/java/io/github/foundationgames/phonos/network/PayloadPackets.java +++ b/src/main/java/io/github/foundationgames/phonos/network/PayloadPackets.java @@ -24,4 +24,10 @@ public static void sendSoundStop(ServerPlayerEntity player, long sourceId) { buf.writeLong(sourceId); ServerPlayNetworking.send(player, Phonos.id("sound_stop"), buf); } + + public static void sendSoundUpdate(ServerPlayerEntity player, SoundEmitterTree.Delta delta) { + var buf = new PacketByteBuf(Unpooled.buffer()); + SoundEmitterTree.Delta.toPacket(buf, delta); + ServerPlayNetworking.send(player, Phonos.id("sound_update"), buf); + } } diff --git a/src/main/java/io/github/foundationgames/phonos/radio/RadioDevice.java b/src/main/java/io/github/foundationgames/phonos/radio/RadioDevice.java new file mode 100644 index 0000000..441dd22 --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/radio/RadioDevice.java @@ -0,0 +1,25 @@ +package io.github.foundationgames.phonos.radio; + +import io.github.foundationgames.phonos.sound.emitter.SoundEmitter; +import io.github.foundationgames.phonos.util.UniqueId; + +import java.util.function.LongConsumer; + +public interface RadioDevice { + int getChannel(); + + interface Transmitter extends RadioDevice, SoundEmitter { + @Override + default void forEachChild(LongConsumer action) { + action.accept(UniqueId.ofRadioChannel(getChannel())); + } + } + + interface Receiver extends RadioDevice { + void setAndUpdateChannel(int channel); + + void addReceiver(); + + void removeReceiver(); + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/radio/RadioStorage.java b/src/main/java/io/github/foundationgames/phonos/radio/RadioStorage.java new file mode 100644 index 0000000..834f7df --- /dev/null +++ b/src/main/java/io/github/foundationgames/phonos/radio/RadioStorage.java @@ -0,0 +1,145 @@ +package io.github.foundationgames.phonos.radio; + +import io.github.foundationgames.phonos.sound.emitter.SoundEmitter; +import io.github.foundationgames.phonos.sound.emitter.SoundEmitterStorage; +import io.github.foundationgames.phonos.sound.emitter.SoundSource; +import io.github.foundationgames.phonos.util.UniqueId; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.registry.RegistryKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.LongConsumer; + +public class RadioStorage { + public static final int CHANNEL_COUNT = 30; + public static final LongList RADIO_EMITTERS = new LongArrayList(); + + private static RadioStorage CLIENT; + private static final Map, RadioStorage> SERVER = new HashMap<>(); + + private static final RadioStorage INVALID = new RadioStorage() {}; + + private final Channel[] channels; + + public RadioStorage() { + channels = new Channel[CHANNEL_COUNT]; + for (int i = 0; i < CHANNEL_COUNT; i++) { + channels[i] = new Channel(i, new LongArrayList(), new ArrayList<>()); + } + } + + public LongList getReceivingEmitters(int channel) { + return channels[channel].receivingEmitters(); + } + + public void addReceivingEmitter(int channel, E receiver) { + var list = getReceivingEmitters(channel); + long id = receiver.emitterId(); + + if (!list.contains(id)) { + list.add(id); + } + } + + public void removeReceivingEmitter(int channel, long emitterId) { + getReceivingEmitters(channel).rem(emitterId); + } + + public List getReceivingSources(int channel) { + return channels[channel].receivingSources(); + } + + public void addReceivingSource(int channel, SoundSource receiver) { + var list = getReceivingSources(channel); + + if (!list.contains(receiver)) { + list.add(receiver); + } + } + + public void removeReceivingSource(int channel, SoundSource receiver) { + getReceivingSources(channel).remove(receiver); + } + + public static RadioStorage getInstance(World world) { + if (world.isClient()) { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + if (CLIENT == null) { + CLIENT = new RadioStorage(); + } + + return CLIENT; + } + } + + if (world instanceof ServerWorld sWorld) { + return SERVER.computeIfAbsent(sWorld.getRegistryKey(), w -> new RadioStorage()); + } + + return INVALID; + } + + public static void serverReset() { + SERVER.clear(); + } + + public static void clientReset() { + CLIENT = null; + } + + public record Channel(int number, LongList receivingEmitters, List receivingSources) {} + + public static class RadioEmitter implements SoundEmitter { + public final int channel; + + private final World world; + private final long emitterId; + + public RadioEmitter(World world, int channel) { + this.world = world; + this.channel = channel; + this.emitterId = UniqueId.ofRadioChannel(channel); + } + + @Override + public long emitterId() { + return this.emitterId; + } + + @Override + public void forEachSource(Consumer action) { + var radio = RadioStorage.getInstance(world); + + for (var source : radio.getReceivingSources(this.channel)) { + action.accept(source); + } + } + + @Override + public void forEachChild(LongConsumer action) { + var emitters = SoundEmitterStorage.getInstance(world); + var radio = RadioStorage.getInstance(world); + + for (long rec : radio.getReceivingEmitters(this.channel)) if (emitters.isLoaded(rec)) { + action.accept(rec); + } + } + } + + public static void init() { + for (int i = 0; i < CHANNEL_COUNT; i++) { + final int channel = i; + SoundEmitterStorage.DEFAULT_EMITTERS.add(w -> new RadioEmitter(w, channel)); + RADIO_EMITTERS.add(UniqueId.ofRadioChannel(channel)); + } + } +} diff --git a/src/main/java/io/github/foundationgames/phonos/sound/ClientSoundStorage.java b/src/main/java/io/github/foundationgames/phonos/sound/ClientSoundStorage.java index 4bed39c..7eb2cbd 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/ClientSoundStorage.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/ClientSoundStorage.java @@ -32,6 +32,8 @@ public static void registerProvider(SoundData.Type type @Override public void play(World world, SoundData data, SoundEmitterTree tree) { + tree.updateClient(world); + var inst = provideSound(data, tree, world.getRandom()); MinecraftClient.getInstance().getSoundManager().play(inst); @@ -56,6 +58,15 @@ public void stop(World world, long soundUniqueId) { activeEmitterTrees.removeIf(tree -> tree.rootId == soundUniqueId); } + @Override + public void update(SoundEmitterTree.Delta delta) { + for (var tree : activeEmitterTrees) { + if (tree.rootId == delta.rootId()) { + delta.apply(tree); + } + } + } + @Override public void tick(World world) { this.playingSounds.long2ObjectEntrySet().removeIf(e -> { @@ -67,7 +78,7 @@ public void tick(World world) { return false; }); - this.activeEmitterTrees.forEach(tree -> tree.update(world)); + this.activeEmitterTrees.forEach(tree -> tree.updateClient(world)); } public interface SoundInstanceFactory { diff --git a/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java b/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java index 6b4df6d..1516236 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/MultiSourceSoundInstance.java @@ -24,6 +24,8 @@ public MultiSourceSoundInstance(SoundEmitterTree tree, SoundEvent sound, Random this.emitters = new AtomicReference<>(tree); this.volume = volume; this.pitch = pitch; + + this.updatePosition(); } @Override diff --git a/src/main/java/io/github/foundationgames/phonos/sound/ServerSoundStorage.java b/src/main/java/io/github/foundationgames/phonos/sound/ServerSoundStorage.java index b800588..3f5050b 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/ServerSoundStorage.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/ServerSoundStorage.java @@ -9,7 +9,7 @@ public class ServerSoundStorage extends SoundStorage { @Override public void play(World world, SoundData data, SoundEmitterTree tree) { - tree.update(world); + tree.updateServer(world); if (world instanceof ServerWorld sWorld) for (var player : sWorld.getPlayers()) { PayloadPackets.sendSoundPlay(player, data, tree); @@ -25,6 +25,10 @@ public void stop(World world, long soundUniqueId) { } } + @Override + public void update(SoundEmitterTree.Delta delta) { + } + @Override public void tick(World world) { } diff --git a/src/main/java/io/github/foundationgames/phonos/sound/SoundStorage.java b/src/main/java/io/github/foundationgames/phonos/sound/SoundStorage.java index c025782..2251b01 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/SoundStorage.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/SoundStorage.java @@ -5,6 +5,7 @@ import io.github.foundationgames.phonos.world.sound.data.SoundData; import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.registry.RegistryKey; import net.minecraft.server.world.ServerWorld; import net.minecraft.world.World; @@ -13,7 +14,7 @@ public abstract class SoundStorage { private static SoundStorage CLIENT; - private static final Map SERVER = new HashMap<>(); + private static final Map, SoundStorage> SERVER = new HashMap<>(); private static final SoundStorage INVALID = new SoundStorage() { @Override public void play(World world, SoundData data, SoundEmitterTree tree) { @@ -25,6 +26,11 @@ public void stop(World world, long emitterId) { Phonos.LOG.error("Stopping " + Long.toHexString(emitterId) + " in an invalid world"); } + @Override + public void update(SoundEmitterTree.Delta delta) { + Phonos.LOG.error("Updating " + Long.toHexString(delta.rootId()) + " in an invalid world"); + } + @Override public void tick(World world) { } @@ -38,7 +44,9 @@ protected void notifySoundSourcesPlayed(World world, SoundData data, SoundEmitte public abstract void play(World world, SoundData data, SoundEmitterTree tree); - public abstract void stop(World world, long soundUniqueId); + public abstract void stop(World world, long emitterId); + + public abstract void update(SoundEmitterTree.Delta delta); public abstract void tick(World world); @@ -54,7 +62,7 @@ public static SoundStorage getInstance(World world) { } if (world instanceof ServerWorld sWorld) { - return SERVER.computeIfAbsent(sWorld, w -> new ServerSoundStorage()); + return SERVER.computeIfAbsent(sWorld.getRegistryKey(), w -> new ServerSoundStorage()); } return INVALID; diff --git a/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterStorage.java b/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterStorage.java index 1606c00..7e3481a 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterStorage.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterStorage.java @@ -5,16 +5,23 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.registry.RegistryKey; import net.minecraft.server.world.ServerWorld; import net.minecraft.world.World; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.Function; public class SoundEmitterStorage { private static SoundEmitterStorage CLIENT; - private static final Map SERVER = new HashMap<>(); - private static final SoundEmitterStorage INVALID = new SoundEmitterStorage() { + private static final Map, SoundEmitterStorage> SERVER = new HashMap<>(); + + public static final List> DEFAULT_EMITTERS = new ArrayList<>(); + + private static final SoundEmitterStorage INVALID = new SoundEmitterStorage(null) { @Override public boolean isLoaded(long uniqueId) { Phonos.LOG.error("Tried to query emitter " + Long.toHexString(uniqueId) + " in invalid world"); @@ -40,6 +47,12 @@ public void removeEmitter(long uniqueId) { private final Long2ObjectMap emitters = new Long2ObjectOpenHashMap<>(); + protected SoundEmitterStorage(World world) { + if (world != null) for (var factory : DEFAULT_EMITTERS) { + this.addEmitter(factory.apply(world)); + } + } + public boolean isLoaded(long uniqueId) { return emitters.containsKey(uniqueId); } @@ -64,7 +77,7 @@ public static SoundEmitterStorage getInstance(World world) { if (world.isClient()) { if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { if (CLIENT == null) { - CLIENT = new SoundEmitterStorage(); + CLIENT = new SoundEmitterStorage(world); } return CLIENT; @@ -72,7 +85,7 @@ public static SoundEmitterStorage getInstance(World world) { } if (world instanceof ServerWorld sWorld) { - return SERVER.computeIfAbsent(sWorld, w -> new SoundEmitterStorage()); + return SERVER.computeIfAbsent(sWorld.getRegistryKey(), w -> new SoundEmitterStorage(world)); } return INVALID; diff --git a/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterTree.java b/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterTree.java index 8096198..61666da 100644 --- a/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterTree.java +++ b/src/main/java/io/github/foundationgames/phonos/sound/emitter/SoundEmitterTree.java @@ -1,6 +1,10 @@ package io.github.foundationgames.phonos.sound.emitter; +import io.github.foundationgames.phonos.radio.RadioStorage; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import net.minecraft.network.PacketByteBuf; import net.minecraft.world.World; @@ -34,7 +38,61 @@ public boolean contains(long value, int upUntil) { return false; } - public void update(World world) { + // Updates the tree on the server and provides a list of changes to be sent to the client + // More accurate than the updateClient() method, as far as what the server is aware of (loaded chunks) + public Delta updateServer(World world) { + var emitters = SoundEmitterStorage.getInstance(world); + var delta = new Delta(this.rootId, new Int2ObjectOpenHashMap<>()); + + int index = 0; + + while (index < this.levels.size() && !this.levels.get(index).empty()) { + if (index + 1 == this.levels.size()) { + this.levels.add(new Level(new LongArrayList(), new LongArrayList())); + } + + var level = this.levels.get(index); + var nextLevel = this.levels.get(index + 1); + + var nextLevelChanges = new ChangeList(new LongArrayList(), new LongArrayList(nextLevel.active())); + + for (long emId : level.active()) { + if (emitters.isLoaded(emId)) { + var emitter = emitters.getEmitter(emId); + + final int searchUntil = index; + emitter.forEachChild(child -> { + if (this.contains(child, searchUntil)) { + return; + } + + nextLevelChanges.remove().rem(child); + + if (!nextLevel.active().contains(child)) { + nextLevelChanges.add().add(child); + } + }); + } + } + + if (!nextLevelChanges.add().isEmpty() || !nextLevelChanges.remove().isEmpty()) { + nextLevelChanges.apply(nextLevel); + delta.deltas().put(index + 1, new Level( + new LongArrayList(nextLevel.active()), + new LongArrayList(nextLevel.inactive()) + )); + } + + index++; + } + + return delta; + } + + // Updates the tree on the client side + // Not entirely accurate, but enough so that most modifications of sound networks will have + // immediate effects regardless of server speed/latency + public void updateClient(World world) { var emitters = SoundEmitterStorage.getInstance(world); int index = 0; @@ -49,6 +107,10 @@ public void update(World world) { nextLevel.inactive().addAll(nextLevel.active()); nextLevel.active().clear(); + for (long l : nextLevel.inactive()) if (RadioStorage.RADIO_EMITTERS.contains(l)) { + nextLevel.active().add(l); + } + nextLevel.inactive().removeAll(nextLevel.active()); for (long emId : level.active()) { if (emitters.isLoaded(emId)) { @@ -71,6 +133,12 @@ public void update(World world) { index++; } + + if (index < this.levels.size() && this.levels.get(index).empty()) { + while (index < this.levels.size()) { + this.levels.remove(this.levels.size() - 1); + } + } } public void forEachSource(World world, Consumer action) { @@ -85,7 +153,7 @@ public void forEachSource(World world, Consumer action) { } } - public record Level(LongArrayList active, LongArrayList inactive){ + public record Level(LongArrayList active, LongArrayList inactive) { public boolean empty() { return active.isEmpty() && inactive.isEmpty(); } @@ -111,4 +179,59 @@ public void toPacket(PacketByteBuf buf) { public static SoundEmitterTree fromPacket(PacketByteBuf buf) { return new SoundEmitterTree(buf.readLong(), buf.readCollection(ArrayList::new, Level::fromPacket)); } + + public record Delta(long rootId, Int2ObjectMap deltas) { + public static void toPacket(PacketByteBuf buf, Delta delta) { + buf.writeLong(delta.rootId); + buf.writeMap(delta.deltas, PacketByteBuf::writeInt, Level::toPacket); + } + + public static Delta fromPacket(PacketByteBuf buf) { + var id = buf.readLong(); + var deltas = buf.readMap(Int2ObjectOpenHashMap::new, PacketByteBuf::readInt, Level::fromPacket); + + return new Delta(id, deltas); + } + + public boolean hasChanges() { + return this.deltas.size() > 0; + } + + public void apply(SoundEmitterTree tree) { + for (var entry : this.deltas().int2ObjectEntrySet()) { + int idx = entry.getIntKey(); + if (idx < tree.levels.size()) { + tree.levels.set(idx, entry.getValue()); + } else { + tree.levels.add(idx, entry.getValue()); + } + } + } + } + + public record ChangeList(LongList add, LongList remove) { + public static void toPacket(PacketByteBuf buf, ChangeList level) { + buf.writeCollection(level.add, PacketByteBuf::writeLong); + buf.writeCollection(level.remove, PacketByteBuf::writeLong); + } + + public static ChangeList fromPacket(PacketByteBuf buf) { + var add = buf.readCollection(LongArrayList::new, PacketByteBuf::readLong); + var rem = buf.readCollection(LongArrayList::new, PacketByteBuf::readLong); + + return new ChangeList(add, rem); + } + + public void apply(Level level) { + level.active().removeAll(this.remove); + level.inactive().removeAll(this.remove); + + for (long l : this.add) { + if (!level.active().contains(l)) { + level.active().add(l); + } + level.inactive().rem(l); + } + } + } } diff --git a/src/main/java/io/github/foundationgames/phonos/util/UniqueId.java b/src/main/java/io/github/foundationgames/phonos/util/UniqueId.java index 6d9033e..2f2d8c8 100644 --- a/src/main/java/io/github/foundationgames/phonos/util/UniqueId.java +++ b/src/main/java/io/github/foundationgames/phonos/util/UniqueId.java @@ -12,4 +12,8 @@ public static long random() { public static long ofBlock(BlockPos pos) { return new Random(pos.asLong() + 0xABCDEF).nextLong(); } + + public static long ofRadioChannel(int channel) { + return new Random(channel + 0xFADECAB).nextLong(); + } } diff --git a/src/main/resources/assets/phonos/blockstates/radio_loudspeaker.json b/src/main/resources/assets/phonos/blockstates/radio_loudspeaker.json new file mode 100644 index 0000000..bcb9157 --- /dev/null +++ b/src/main/resources/assets/phonos/blockstates/radio_loudspeaker.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": {"model": "phonos:block/radio_loudspeaker"}, + "facing=south": {"model": "phonos:block/radio_loudspeaker", "y": 180}, + "facing=east": {"model": "phonos:block/radio_loudspeaker", "y": 90}, + "facing=west": {"model": "phonos:block/radio_loudspeaker", "y": 270} + } +} \ No newline at end of file diff --git a/src/main/resources/assets/phonos/blockstates/radio_transceiver.json b/src/main/resources/assets/phonos/blockstates/radio_transceiver.json new file mode 100644 index 0000000..abb59c2 --- /dev/null +++ b/src/main/resources/assets/phonos/blockstates/radio_transceiver.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=east": {"model": "phonos:block/radio_transceiver", "y": 90}, + "facing=north": {"model": "phonos:block/radio_transceiver"}, + "facing=south": {"model": "phonos:block/radio_transceiver", "y": 180}, + "facing=west": {"model": "phonos:block/radio_transceiver", "y": 270} + } +} \ No newline at end of file diff --git a/src/main/resources/assets/phonos/lang/en_us.json b/src/main/resources/assets/phonos/lang/en_us.json index 80dde47..634ebdc 100644 --- a/src/main/resources/assets/phonos/lang/en_us.json +++ b/src/main/resources/assets/phonos/lang/en_us.json @@ -5,6 +5,8 @@ "block.phonos.electronic_jukebox": "Electronic Jukebox", "block.phonos.electronic_note_block": "Electronic Note Block", "block.phonos.connection_hub": "Connection Hub", + "block.phonos.radio_transceiver": "Radio Transceiver", + "block.phonos.radio_loudspeaker": "Radio Loudspeaker", "item.phonos.audio_cable": "Audio Cable", "item.phonos.black_audio_cable": "Black Audio Cable", diff --git a/src/main/resources/assets/phonos/models/block/radio_loudspeaker.json b/src/main/resources/assets/phonos/models/block/radio_loudspeaker.json new file mode 100644 index 0000000..12dce80 --- /dev/null +++ b/src/main/resources/assets/phonos/models/block/radio_loudspeaker.json @@ -0,0 +1,62 @@ +{ + "parent": "block/block", + "textures": { + "0": "phonos:block/radio_antenna", + "1": "phonos:block/loudspeaker", + "2": "phonos:block/loudspeaker_east", + "3": "phonos:block/loudspeaker_west", + "4": "phonos:block/stone_base", + "5": "phonos:block/radio_loudspeaker_back", + "6": "phonos:block/radio_loudspeaker_top", + "particle": "phonos:block/radio_antenna" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 16, 16], "texture": "#1", "cullface": "north"}, + "east": {"uv": [0, 0, 16, 16], "texture": "#2", "cullface": "east"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#5", "cullface": "south"}, + "west": {"uv": [0, 0, 16, 16], "texture": "#3", "cullface": "west"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#6", "cullface": "up"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#4", "cullface": "down"} + } + }, + { + "from": [7, 7.99, 8], + "to": [9, 16.99, 8], + "rotation": {"angle": 45, "axis": "y", "origin": [8, 7, 8]}, + "faces": { + "north": {"uv": [7, 1, 9, 10], "texture": "#0"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#0"}, + "south": {"uv": [7, 1, 9, 10], "texture": "#0"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#0"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#0"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#0"} + } + }, + { + "from": [0, 16.99, -0.5], + "to": [16, 16.99, 15.5], + "rotation": {"angle": 22.5, "axis": "y", "origin": [8, 7, 8]}, + "faces": { + "north": {"uv": [0, 0, 0, 0], "texture": "#0", "cullface": "up"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#0", "cullface": "up"}, + "south": {"uv": [0, 0, 0, 0], "texture": "#0", "cullface": "up"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#0", "cullface": "up"}, + "up": {"uv": [0, 0, 16, 16], "rotation": 180, "texture": "#0", "cullface": "up"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#0", "cullface": "up"} + } + }, + { + "from": [8, 7.99, 7], + "to": [8, 16.99, 9], + "rotation": {"angle": 45, "axis": "y", "origin": [8, 7, 8]}, + "faces": { + "east": {"uv": [7, 1, 9, 10], "texture": "#0", "cullface": "up"}, + "west": {"uv": [7, 1, 9, 10], "texture": "#0", "cullface": "up"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/phonos/models/block/radio_transceiver.json b/src/main/resources/assets/phonos/models/block/radio_transceiver.json new file mode 100644 index 0000000..7cf6026 --- /dev/null +++ b/src/main/resources/assets/phonos/models/block/radio_transceiver.json @@ -0,0 +1,63 @@ +{ + "parent": "block/block", + "textures": { + "0": "phonos:block/radio_antenna", + "1": "phonos:block/radio_transceiver_front", + "2": "phonos:block/radio_transceiver_side", + "3": "phonos:block/radio_transceiver_top", + "4": "phonos:block/stone_base", + "particle": "phonos:block/radio_transceiver_top" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 7, 16], + "faces": { + "north": {"uv": [0, 9, 16, 16], "texture": "#1"}, + "east": {"uv": [0, 9, 16, 16], "texture": "#2"}, + "south": {"uv": [0, 9, 16, 16], "texture": "#2"}, + "west": {"uv": [0, 9, 16, 16], "texture": "#2"}, + "up": {"uv": [0, 0, 16, 16], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#4"} + } + }, + { + "from": [8, 6.99, 10], + "to": [8, 15.99, 12], + "rotation": {"angle": 45, "axis": "y", "origin": [8, 6, 11]}, + "faces": { + "east": {"uv": [7, 1, 9, 10], "texture": "#0"}, + "west": {"uv": [7, 1, 9, 10], "texture": "#0"} + } + }, + { + "from": [7, 6.99, 11], + "to": [9, 15.99, 11], + "rotation": {"angle": 45, "axis": "y", "origin": [8, 6, 11]}, + "faces": { + "north": {"uv": [7, 1, 9, 10], "texture": "#0"}, + "south": {"uv": [7, 1, 9, 10], "texture": "#0"} + } + }, + { + "from": [0, 15.99, 2.5], + "to": [16, 15.99, 18.5], + "rotation": {"angle": 22.5, "axis": "y", "origin": [8, 6, 11]}, + "faces": { + "up": {"uv": [0, 0, 16, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#0"} + } + }, + { + "from": [6, 7, 9], + "to": [10, 9, 13], + "faces": { + "north": {"uv": [6, 14, 10, 16], "rotation": 180, "texture": "#1"}, + "east": {"uv": [10, 13, 6, 15], "texture": "#1"}, + "south": {"uv": [6, 13, 10, 15], "texture": "#1"}, + "west": {"uv": [10, 14, 6, 16], "rotation": 180, "texture": "#1"}, + "up": {"uv": [6, 3, 10, 7], "rotation": 180, "texture": "#3"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/phonos/models/item/radio_loudspeaker.json b/src/main/resources/assets/phonos/models/item/radio_loudspeaker.json new file mode 100644 index 0000000..baf7759 --- /dev/null +++ b/src/main/resources/assets/phonos/models/item/radio_loudspeaker.json @@ -0,0 +1 @@ +{ "parent": "phonos:block/radio_loudspeaker" } \ No newline at end of file diff --git a/src/main/resources/assets/phonos/models/item/radio_transceiver.json b/src/main/resources/assets/phonos/models/item/radio_transceiver.json new file mode 100644 index 0000000..9c14892 --- /dev/null +++ b/src/main/resources/assets/phonos/models/item/radio_transceiver.json @@ -0,0 +1 @@ +{ "parent": "phonos:block/radio_transceiver" } \ No newline at end of file diff --git a/src/main/resources/assets/phonos/textures/block/loudspeaker_back.png b/src/main/resources/assets/phonos/textures/block/loudspeaker_back.png index ad9c967..f44936c 100644 Binary files a/src/main/resources/assets/phonos/textures/block/loudspeaker_back.png and b/src/main/resources/assets/phonos/textures/block/loudspeaker_back.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_antenna.png b/src/main/resources/assets/phonos/textures/block/radio_antenna.png new file mode 100644 index 0000000..1cf2b6e Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_antenna.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_back.png b/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_back.png new file mode 100644 index 0000000..a61e08a Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_back.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_top.png b/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_top.png new file mode 100644 index 0000000..05d4b28 Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_loudspeaker_top.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_transceiver_front.png b/src/main/resources/assets/phonos/textures/block/radio_transceiver_front.png new file mode 100644 index 0000000..c43b17b Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_transceiver_front.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_transceiver_side.png b/src/main/resources/assets/phonos/textures/block/radio_transceiver_side.png new file mode 100644 index 0000000..cd7f5dc Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_transceiver_side.png differ diff --git a/src/main/resources/assets/phonos/textures/block/radio_transceiver_top.png b/src/main/resources/assets/phonos/textures/block/radio_transceiver_top.png new file mode 100644 index 0000000..c7f6f45 Binary files /dev/null and b/src/main/resources/assets/phonos/textures/block/radio_transceiver_top.png differ diff --git a/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json b/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json index 4c4524d..4cea628 100644 --- a/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json +++ b/src/main/resources/data/minecraft/tags/blocks/mineable/axe.json @@ -4,6 +4,8 @@ "phonos:loudspeaker", "phonos:electronic_note_block", "phonos:electronic_jukebox", - "phonos:connection_hub" + "phonos:connection_hub", + "phonos:radio_transceiver", + "phonos:radio_loudspeaker" ] } \ No newline at end of file