From 2aa1dea5126290ee6dadc0884a3d8e2791be04ef Mon Sep 17 00:00:00 2001 From: SoniEx2 Date: Sat, 15 Mar 2025 18:57:24 -0300 Subject: add everything so far --- .gitignore | 4 +- flite/.gitignore | 1 + flite/build_script.sh | 25 + flite/get_flite.sh | 5 + gui/build_script.sh | 16 + gui/radio-receiver.c | 105 ++++ .../radio/client/mixin/BlockStatesLoaderMixin.java | 43 ++ .../space/autistic/radio/client/ClientProxy.kt | 20 + .../autistic/radio/client/PirateRadioClient.kt | 46 +- .../radio/client/PirateRadioDataGenerator.kt | 4 +- .../radio/client/PirateRadioEntityModelLayers.kt | 1 + .../radio/client/antenna/WasmAntennaFactory.kt | 15 +- .../space/autistic/radio/client/cli/Funny.kt | 16 + .../entity/DisposableTransmitterEntityRenderer.kt | 79 +++ .../entity/ElectronicsTraderEntityRenderer.kt | 2 +- .../autistic/radio/client/fmsim/FmSimulatorMode.kt | 7 + .../autistic/radio/client/gui/FmReceiverScreen.kt | 627 ++++++++++++++++++++- .../radio/client/gui/StorageCardEditScreen.kt | 115 ++++ .../radio/client/sound/PirateRadioSoundInstance.kt | 68 +++ .../radio/client/sound/ReceiverAudioStream.kt | 34 ++ .../4145a4ade350d062a154f42d7ad0d98fb52bf04b | 4 +- .../bd1ee27e4c10ec669c0e0894b64dd83a58902c72 | 3 +- .../pirate-radio/models/item/fm-receiver.json | 6 - .../recipe/disposable-transmitter.json | 3 - .../kotlin/space/autistic/radio/CommonProxy.kt | 10 + .../kotlin/space/autistic/radio/PirateRadio.kt | 59 +- .../space/autistic/radio/PirateRadioComponents.kt | 30 + .../space/autistic/radio/PirateRadioEntityTypes.kt | 26 +- .../space/autistic/radio/PirateRadioItems.kt | 23 +- .../space/autistic/radio/PirateRadioRegistries.kt | 12 + .../autistic/radio/PirateRadioRegistryKeys.kt | 13 + src/main/kotlin/space/autistic/radio/SidedProxy.kt | 9 + .../kotlin/space/autistic/radio/antenna/Antenna.kt | 9 + .../autistic/radio/antenna/AntennaSerializer.kt | 8 + .../space/autistic/radio/antenna/ConstAntenna.kt | 7 + .../radio/antenna/PirateRadioAntennaSerializers.kt | 18 + .../space/autistic/radio/antenna/WasmAntenna.kt | 7 + .../radio/entity/DisposableTransmitterEntity.kt | 178 ++++++ .../radio/entity/ElectronicsTraderEntity.kt | 4 +- .../radio/item/DisposableTransmitterItem.kt | 56 ++ .../space/autistic/radio/item/StorageCardItem.kt | 20 + .../radio/network/StorageCardUpdateC2SPayload.kt | 27 + .../kotlin/space/autistic/radio/wasm/Bindings.kt | 173 ++++++ .../blockstates/disposable-transmitter.json | 10 + .../resources/assets/pirate-radio/lang/en_us.json | 22 +- .../block/disposable-transmitter-vertical.json | 29 + .../models/block/disposable-transmitter.json | 30 + .../textures/entity/electronics-trader.png | Bin 0 -> 1059 bytes .../pirate-radio/textures/gui/radio-receiver.png | Bin 0 -> 1059 bytes .../textures/item/disposable-transmitter.png | Bin 0 -> 881 bytes .../pirate-radio/pirate-radio/antenna/const.json | 4 + .../pirate-radio/pirate-radio/antenna/null.json | 4 + src/main/resources/fabric.mod.json | 6 + src/main/resources/pirate-radio.client-mixins.json | 14 + .../space/autistic/radio/complex/ComplexKtTest.kt | 2 + .../space/autistic/radio/fmsim/TestAsserts.kt | 20 +- 56 files changed, 2021 insertions(+), 58 deletions(-) create mode 100644 flite/.gitignore create mode 100755 flite/build_script.sh create mode 100755 flite/get_flite.sh create mode 100755 gui/build_script.sh create mode 100644 gui/radio-receiver.c create mode 100644 src/client/java/space/autistic/radio/client/mixin/BlockStatesLoaderMixin.java create mode 100644 src/client/kotlin/space/autistic/radio/client/ClientProxy.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/cli/Funny.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/entity/DisposableTransmitterEntityRenderer.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/fmsim/FmSimulatorMode.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/gui/StorageCardEditScreen.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/sound/ReceiverAudioStream.kt delete mode 100644 src/main/generated/assets/pirate-radio/models/item/fm-receiver.json create mode 100644 src/main/kotlin/space/autistic/radio/CommonProxy.kt create mode 100644 src/main/kotlin/space/autistic/radio/PirateRadioComponents.kt create mode 100644 src/main/kotlin/space/autistic/radio/PirateRadioRegistries.kt create mode 100644 src/main/kotlin/space/autistic/radio/PirateRadioRegistryKeys.kt create mode 100644 src/main/kotlin/space/autistic/radio/SidedProxy.kt create mode 100644 src/main/kotlin/space/autistic/radio/antenna/Antenna.kt create mode 100644 src/main/kotlin/space/autistic/radio/antenna/AntennaSerializer.kt create mode 100644 src/main/kotlin/space/autistic/radio/antenna/ConstAntenna.kt create mode 100644 src/main/kotlin/space/autistic/radio/antenna/PirateRadioAntennaSerializers.kt create mode 100644 src/main/kotlin/space/autistic/radio/antenna/WasmAntenna.kt create mode 100644 src/main/kotlin/space/autistic/radio/entity/DisposableTransmitterEntity.kt create mode 100644 src/main/kotlin/space/autistic/radio/item/DisposableTransmitterItem.kt create mode 100644 src/main/kotlin/space/autistic/radio/item/StorageCardItem.kt create mode 100644 src/main/kotlin/space/autistic/radio/network/StorageCardUpdateC2SPayload.kt create mode 100644 src/main/kotlin/space/autistic/radio/wasm/Bindings.kt create mode 100644 src/main/resources/assets/pirate-radio/blockstates/disposable-transmitter.json create mode 100644 src/main/resources/assets/pirate-radio/models/block/disposable-transmitter-vertical.json create mode 100644 src/main/resources/assets/pirate-radio/models/block/disposable-transmitter.json create mode 100644 src/main/resources/assets/pirate-radio/textures/entity/electronics-trader.png create mode 100644 src/main/resources/assets/pirate-radio/textures/gui/radio-receiver.png create mode 100644 src/main/resources/assets/pirate-radio/textures/item/disposable-transmitter.png create mode 100644 src/main/resources/data/pirate-radio/pirate-radio/antenna/const.json create mode 100644 src/main/resources/data/pirate-radio/pirate-radio/antenna/null.json create mode 100644 src/main/resources/pirate-radio.client-mixins.json diff --git a/.gitignore b/.gitignore index bab5adf..863eead 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ replay_*.log *.hprof *.jfr -/src/main/resources/opus.wasm +# wasm + +*.wasm diff --git a/flite/.gitignore b/flite/.gitignore new file mode 100644 index 0000000..95c320b --- /dev/null +++ b/flite/.gitignore @@ -0,0 +1 @@ +/flite-* diff --git a/flite/build_script.sh b/flite/build_script.sh new file mode 100755 index 0000000..4e18760 --- /dev/null +++ b/flite/build_script.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +cd flite-6c9f20dc915b17f5619340069889db0aa007fcdc + +s_EXPORTED_FUNCTIONS="-s EXPORTED_FUNCTIONS=$(printf '_%s,' \ +malloc +)_free" + +shared_flags="-Oz -flto -fno-unroll-loops -fno-inline -mnontrapping-fptoint" +compile_flags="-DNDEBUG -DNO_STDIO $shared_flags" +link_flags="$shared_flags -s MALLOC=emmalloc -s ASSERTIONS=0 -s INITIAL_MEMORY=$((20*1024*1024)) -s TOTAL_STACK=$((2*1024*1024)) -s STANDALONE_WASM=1 -s PURE_WASI=1 -s STACK_OVERFLOW_CHECK=0 -s WASM_BIGINT=1 -s ABORTING_MALLOC=0 -s MEMORY_GROWTH_GEOMETRIC_STEP=0 -s ALLOW_MEMORY_GROWTH=1" + +emconfigure ./configure STRIP='emstrip' CFLAGS="$compile_flags" LDFLAGS="$link_flags" --host=wasm32 --disable-sockets --disable-shared --without-pic --with-mmap=none --with-audio=none +emmake make + +#emcc --no-entry $s_EXPORTED_FUNCTIONS $link_flags libmp3lame/.libs/libmp3lame.a +#em++ --no-entry $s_EXPORTED_FUNCTIONS $link_flags $compile_flags -o pocketfft.wasm impl.cc + +#mkdir -p ../../src/main/resources/flite/ +#cp a.out.wasm ../../src/main/resources/flite.wasm +#cp COPYING ../../src/main/resources/flite/ + +exit 0 diff --git a/flite/get_flite.sh b/flite/get_flite.sh new file mode 100755 index 0000000..bc09d5c --- /dev/null +++ b/flite/get_flite.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +commit=6c9f20dc915b17f5619340069889db0aa007fcdc +wget -O flite-$commit.zip https://github.com/festvox/flite/archive/$commit.zip +unzip flite-$commit.zip diff --git a/gui/build_script.sh b/gui/build_script.sh new file mode 100755 index 0000000..5d7c649 --- /dev/null +++ b/gui/build_script.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +s_EXPORTED_FUNCTIONS="-s EXPORTED_FUNCTIONS=_init,_render" + +shared_flags="-Oz -flto -fno-inline -fno-unroll-loops -mnontrapping-fptoint" +compile_flags="$shared_flags --std=c23" +link_flags="$shared_flags -s MALLOC=emmalloc -s ASSERTIONS=0 -s INITIAL_MEMORY=65536 -s TOTAL_STACK=16384 -s STANDALONE_WASM=1 -s PURE_WASI=1 -s STACK_OVERFLOW_CHECK=0 -s WASM_BIGINT=1 -s ABORTING_MALLOC=0 -s MEMORY_GROWTH_GEOMETRIC_STEP=0 -s ALLOW_MEMORY_GROWTH=1" + +emcc --no-entry $s_EXPORTED_FUNCTIONS $link_flags $compile_flags -o radio-receiver.wasm radio-receiver.c + +mkdir -p ../src/main/resources/assets/pirate-radio/guis +cp radio-receiver.wasm ../src/main/resources/assets/pirate-radio/guis/radio-receiver.wasm + +exit 0 diff --git a/gui/radio-receiver.c b/gui/radio-receiver.c new file mode 100644 index 0000000..fef0858 --- /dev/null +++ b/gui/radio-receiver.c @@ -0,0 +1,105 @@ +#include + +enum logger_level : int { + LOG_TRACE = 0, + LOG_DEBUG = 10, + LOG_INFO = 20, + LOG_WARN = 30, + LOG_ERROR = 40, +}; + +[[clang::import_module("logger")]] [[clang::import_name("log")]] +extern void logger_log(void); +[[clang::import_module("logger")]] [[clang::import_name("log-message")]] +extern void logger_log_message(char *message); +[[clang::import_module("logger")]] [[clang::import_name("add-argument-int")]] +extern void logger_add_argument_int(int arg); +[[clang::import_module("logger")]] [[clang::import_name("begin")]] +extern void logger_log_begin(enum logger_level level); + +[[clang::import_module("screen")]] [[clang::import_name("set-background-size")]] +extern void set_background_size(int width, int height); +[[clang::import_module("screen")]] [[clang::import_name("set-background-texture-size")]] +extern void set_background_texture_size(int width, int height); + +[[clang::import_module("text")]] [[clang::import_name("free")]] +extern void text_free(int handle); +[[clang::import_module("text")]] [[clang::import_name("literal")]] +extern int text_literal(char *text); +[[clang::import_module("text")]] [[clang::import_name("translatable")]] +extern int text_translatable(char *key); +[[clang::import_module("text")]] [[clang::import_name("translatable-arguments")]] +extern int text_arguments(char *key, int size, int *args); +[[clang::import_module("screen")]] [[clang::import_name("render-text-object")]] +extern void render_text_object(int handle, int x, int y, int color, bool shadow); + +enum simulator_mode : int { + SIMULATOR_FULL = 0, + SIMULATOR_FAST = 1, + SIMULATOR_DEAF = 2, +}; + +[[clang::import_module("simulator")]] [[clang::import_name("get-mode")]] +extern enum simulator_mode simulator_get_mode(); +[[clang::import_module("simulator")]] [[clang::import_name("get-volume")]] +extern int simulator_get_volume(); +[[clang::import_module("simulator")]] [[clang::import_name("get-frequency")]] +extern int simulator_get_frequency(); + +void init() { + logger_log_begin(LOG_INFO); + logger_log_message("default radio skin."); + set_background_size(100, 100); +} + +void render(int mouseX, int mouseY, float delta) { + int t1; + switch (simulator_get_mode()) { + case SIMULATOR_FAST: + { + t1 = text_translatable("pirate-radio.mode.fast"); + break; + } + case SIMULATOR_DEAF: + { + t1 = text_translatable("pirate-radio.mode.deaf"); + break; + } + default: + { + t1 = text_translatable("pirate-radio.mode.full"); + } + } + int t2 = text_arguments("pirate-radio.mode.selected", 1, &t1); + render_text_object(t2, 200, 100, -1, true); + text_free(t1); + text_free(t2); + + int volume = simulator_get_volume(); + if (volume == 0) { + t1 = text_translatable("pirate-radio.volume.off"); + } else { + char volume_text[30]; + sprintf(volume_text, "%d", volume); + t1 = text_literal(volume_text); + } + t2 = text_arguments("pirate-radio.volume.selected", 1, &t1); + render_text_object(t2, 200, 120, -1, true); + text_free(t1); + text_free(t2); + + int frequency = simulator_get_frequency(); + int freq_mhz = frequency/10; + int freq_step = frequency%10; + char freq_text[30]; + sprintf(freq_text, "%d", freq_mhz); + t1 = text_literal(freq_text); + sprintf(freq_text, "%d", freq_step); + t2 = text_literal(freq_text); + int freq_args[2] = {t1, t2}; + int t3 = text_arguments("pirate-radio.frequency.selected", 2, freq_args); + render_text_object(t3, 200, 140, -1, true); + text_free(t1); + text_free(t2); + text_free(t3); +} diff --git a/src/client/java/space/autistic/radio/client/mixin/BlockStatesLoaderMixin.java b/src/client/java/space/autistic/radio/client/mixin/BlockStatesLoaderMixin.java new file mode 100644 index 0000000..0e9b05e --- /dev/null +++ b/src/client/java/space/autistic/radio/client/mixin/BlockStatesLoaderMixin.java @@ -0,0 +1,43 @@ +package space.autistic.radio.client.mixin; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.render.model.BlockStatesLoader; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Property; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; +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; + +@Mixin(BlockStatesLoader.class) +public abstract class BlockStatesLoaderMixin { + + @Shadow + abstract void loadBlockStates(Identifier id, StateManager stateManager); + + @Shadow + private Profiler profiler; + + @Unique + private static final StateManager STATE_MANAGER = new StateManager.Builder(Blocks.AIR) + .add(DirectionProperty.of("facing")) + .build(Block::getDefaultState, BlockState::new); + + @Inject( + method = {"load()V"}, + at = {@At("HEAD")} + ) + void onLoad(CallbackInfo callbackInfo) { + profiler.push("pirate_radio_static_definitions"); + loadBlockStates(Identifier.of("pirate-radio", "disposable-transmitter"), STATE_MANAGER); + profiler.pop(); + } +} diff --git a/src/client/kotlin/space/autistic/radio/client/ClientProxy.kt b/src/client/kotlin/space/autistic/radio/client/ClientProxy.kt new file mode 100644 index 0000000..3c29cd3 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/ClientProxy.kt @@ -0,0 +1,20 @@ +package space.autistic.radio.client + +import net.minecraft.client.MinecraftClient +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.ItemStack +import net.minecraft.util.Hand +import space.autistic.radio.CommonProxy +import space.autistic.radio.PirateRadioItems.STORAGE_CARD +import space.autistic.radio.client.gui.StorageCardEditScreen + +class ClientProxy : CommonProxy() { + override fun useStorageCard(player: PlayerEntity, item: ItemStack, hand: Hand) { + if (player is ClientPlayerEntity) { + if (item.isOf(STORAGE_CARD)) { + MinecraftClient.getInstance().setScreen(StorageCardEditScreen(player, item, hand)) + } + } + } +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/PirateRadioClient.kt b/src/client/kotlin/space/autistic/radio/client/PirateRadioClient.kt index 54b7640..1a68c21 100644 --- a/src/client/kotlin/space/autistic/radio/client/PirateRadioClient.kt +++ b/src/client/kotlin/space/autistic/radio/client/PirateRadioClient.kt @@ -1,25 +1,47 @@ package space.autistic.radio.client -import com.mojang.brigadier.CommandDispatcher import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry import net.minecraft.client.MinecraftClient -import net.minecraft.command.CommandRegistryAccess +import net.minecraft.client.sound.SoundInstance +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.ItemStack +import net.minecraft.util.Hand import org.slf4j.LoggerFactory import space.autistic.radio.PirateRadio import space.autistic.radio.PirateRadio.MOD_ID import space.autistic.radio.PirateRadioEntityTypes import space.autistic.radio.client.entity.ElectronicsTraderEntityRenderer +import space.autistic.radio.client.entity.DisposableTransmitterEntityRenderer +import space.autistic.radio.client.fmsim.FmSimulatorMode import space.autistic.radio.client.gui.FmReceiverScreen +import space.autistic.radio.client.sound.PirateRadioSoundInstance +import kotlin.math.max +import kotlin.math.min object PirateRadioClient : ClientModInitializer { - private val logger = LoggerFactory.getLogger(MOD_ID) + private var soundInstance: SoundInstance? = null + var volume: Int = 0 + set(value) { + field = min(10, max(0, value)) + } + var stereo: Boolean = false + var frequency = 768 + set(value) { + field = min(1080, max(768, value)) + } + var mode = FmSimulatorMode.FULL override fun onInitializeClient() { + PirateRadio.proxy = ClientProxy() EntityRendererRegistry.register(PirateRadioEntityTypes.ELECTRONICS_TRADER, ::ElectronicsTraderEntityRenderer) + EntityRendererRegistry.register( + PirateRadioEntityTypes.DISPOSABLE_TRANSMITTER, + ::DisposableTransmitterEntityRenderer + ) PirateRadioEntityModelLayers.initialize() ClientCommandRegistrationCallback.EVENT.register { dispatcher, _ -> dispatcher.register( @@ -31,5 +53,21 @@ object PirateRadioClient : ClientModInitializer { } ) } + ClientTickEvents.END_WORLD_TICK.register { world -> + if (volume > 0 && MinecraftClient.getInstance().player?.isRemoved == false) { + if (soundInstance == null) { + soundInstance = PirateRadioSoundInstance(MinecraftClient.getInstance().player!!) + } + val soundManager = MinecraftClient.getInstance().soundManager + if (!soundManager.isPlaying(soundInstance)) { + soundManager.play(soundInstance) + } + } else { + if (soundInstance != null) { + MinecraftClient.getInstance().soundManager.stop(soundInstance) + soundInstance = null + } + } + } } } \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/PirateRadioDataGenerator.kt b/src/client/kotlin/space/autistic/radio/client/PirateRadioDataGenerator.kt index b5130a1..65e9677 100644 --- a/src/client/kotlin/space/autistic/radio/client/PirateRadioDataGenerator.kt +++ b/src/client/kotlin/space/autistic/radio/client/PirateRadioDataGenerator.kt @@ -32,7 +32,7 @@ class PirateRadioItemModelGenerator(output: FabricDataOutput) : FabricModelProvi modelGenderator.register(PirateRadioItems.SBC, Models.GENERATED) modelGenderator.register(PirateRadioItems.WIRE, Models.GENERATED) modelGenderator.register(PirateRadioItems.POWERBANK, Models.GENERATED) - modelGenderator.register(PirateRadioItems.FM_RECEIVER, Models.GENERATED) + //modelGenderator.register(PirateRadioItems.FM_RECEIVER, Models.GENERATED) modelGenderator.register(PirateRadioItems.STORAGE_CARD, Models.GENERATED) modelGenderator.register(PirateRadioItems.DISPOSABLE_TRANSMITTER, Models.GENERATED) } @@ -46,7 +46,7 @@ class PirateRadioRecipeGenerator( override fun generate(exporter: RecipeExporter) { ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, PirateRadioItems.DISPOSABLE_TRANSMITTER) .input(PirateRadioItems.SBC).input(PirateRadioItems.WIRE).input(PirateRadioItems.POWERBANK) - .input(PirateRadioItems.STORAGE_CARD) + //.input(PirateRadioItems.STORAGE_CARD) .criterion("has_sbc", RecipeProvider.conditionsFromItem(PirateRadioItems.SBC)).offerTo(exporter) } diff --git a/src/client/kotlin/space/autistic/radio/client/PirateRadioEntityModelLayers.kt b/src/client/kotlin/space/autistic/radio/client/PirateRadioEntityModelLayers.kt index 765912d..604fdfd 100644 --- a/src/client/kotlin/space/autistic/radio/client/PirateRadioEntityModelLayers.kt +++ b/src/client/kotlin/space/autistic/radio/client/PirateRadioEntityModelLayers.kt @@ -9,6 +9,7 @@ import space.autistic.radio.PirateRadio object PirateRadioEntityModelLayers { val ELECTRONICS_TRADER = EntityModelLayer(Identifier.of(PirateRadio.MOD_ID, "electronics-trader"), "main") + val PIRATE_RADIO = EntityModelLayer(Identifier.of(PirateRadio.MOD_ID, "electronics-trader"), "main") fun initialize() { EntityModelLayerRegistry.registerModelLayer(ELECTRONICS_TRADER) { diff --git a/src/client/kotlin/space/autistic/radio/client/antenna/WasmAntennaFactory.kt b/src/client/kotlin/space/autistic/radio/client/antenna/WasmAntennaFactory.kt index 51743dd..38d0c97 100644 --- a/src/client/kotlin/space/autistic/radio/client/antenna/WasmAntennaFactory.kt +++ b/src/client/kotlin/space/autistic/radio/client/antenna/WasmAntennaFactory.kt @@ -12,9 +12,7 @@ import com.dylibso.chicory.wasm.types.Value import com.dylibso.chicory.wasm.types.ValueType import org.joml.Quaterniond import org.joml.Vector3d -import space.autistic.radio.PirateRadio -import java.util.logging.Level -import java.util.logging.Logger +import space.autistic.radio.PirateRadio.logger class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { var failing = false @@ -25,7 +23,7 @@ class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { // capped at 1MB per antenna .withMemoryLimits(MemoryLimits(0, 16)) } catch (e: ChicoryException) { - logger.log(Level.SEVERE, "Error while trying to parse antenna model.", e) + logger.error("Error while trying to parse antenna model.", e) failing = true null } @@ -53,9 +51,7 @@ class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { orientation.w.toRawBits() ) if (instance.exports().global("should-attenuate").type != ValueType.I32) { - logger.log( - Level.SEVERE, "Error while trying to initialize antenna model: missing 'should-attenuate'" - ) + logger.error("Error while trying to initialize antenna model: missing 'should-attenuate'") failing = true return ConstAntennaModel(0f) } @@ -73,7 +69,7 @@ class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { )[0] ) } catch (e: ChicoryException) { - logger.log(Level.SEVERE, "Error while trying to evaluate antenna model.", e) + logger.error("Error while trying to evaluate antenna model.", e) failing = true return 0f } @@ -84,7 +80,7 @@ class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { } } } catch (e: ChicoryException) { - logger.log(Level.SEVERE, "Error while trying to initialize antenna model.", e) + logger.error("Error while trying to initialize antenna model.", e) failing = true return ConstAntennaModel(0f) } @@ -92,6 +88,5 @@ class WasmAntennaFactory(moduleBytes: ByteArray) : AntennaModelFactory { companion object { private val defaultImports = ImportValues.builder().build() - private val logger = Logger.getLogger(PirateRadio.MOD_ID) } } \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt b/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt new file mode 100644 index 0000000..ebf6b06 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt @@ -0,0 +1,16 @@ +package space.autistic.radio.client.cli + +import space.autistic.radio.wasm.Bindings +import java.lang.invoke.MethodHandles +import kotlin.reflect.jvm.javaMethod + +object Funny { + val lookup = MethodHandles.lookup() + + fun test() { + } +} + +fun main() { + Bindings.bindFunc("", "", Funny.lookup, Funny::test.javaMethod!!, Funny) +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/entity/DisposableTransmitterEntityRenderer.kt b/src/client/kotlin/space/autistic/radio/client/entity/DisposableTransmitterEntityRenderer.kt new file mode 100644 index 0000000..61b1e19 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/entity/DisposableTransmitterEntityRenderer.kt @@ -0,0 +1,79 @@ +package space.autistic.radio.client.entity + +import net.minecraft.client.render.OverlayTexture +import net.minecraft.client.render.TexturedRenderLayers +import net.minecraft.client.render.VertexConsumerProvider +import net.minecraft.client.render.entity.EntityRenderer +import net.minecraft.client.render.entity.EntityRendererFactory +import net.minecraft.client.render.model.BakedModelManager +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.screen.PlayerScreenHandler +import net.minecraft.util.Identifier +import net.minecraft.util.math.Direction +import net.minecraft.util.math.RotationAxis +import space.autistic.radio.PirateRadio +import space.autistic.radio.entity.DisposableTransmitterEntity + +class DisposableTransmitterEntityRenderer(ctx: EntityRendererFactory.Context) : + EntityRenderer(ctx) { + + private val blockRenderManager = ctx.blockRenderManager + + override fun getTexture(entity: DisposableTransmitterEntity): Identifier { + return PlayerScreenHandler.BLOCK_ATLAS_TEXTURE + } + + override fun render( + entity: DisposableTransmitterEntity, + yaw: Float, + tickDelta: Float, + matrices: MatrixStack, + vertexConsumers: VertexConsumerProvider, + light: Int + ) { + super.render(entity, yaw, tickDelta, matrices, vertexConsumers, light) + + matrices.push() + val facing: Direction = entity.horizontalFacing + val vec3d = this.getPositionOffset(entity, tickDelta) + matrices.translate(-vec3d.getX(), -vec3d.getY(), -vec3d.getZ()) + val d = (1.0 - DisposableTransmitterEntity.DEPTH) / 2.0 + matrices.translate( + facing.offsetX.toDouble() * d, facing.offsetY.toDouble() * d, facing.offsetZ.toDouble() * d + ) + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(entity.pitch)) + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180.0f - entity.yaw)) + if (!entity.isInvisible) { + val bakedModelManager: BakedModelManager = this.blockRenderManager.models.modelManager + matrices.push() + matrices.translate(-0.5f, -0.5f, -0.5f) + this.blockRenderManager.modelRenderer.render( + matrices.peek(), + vertexConsumers.getBuffer(TexturedRenderLayers.getEntitySolid()), + null, + bakedModelManager.getModel(MODEL_ID[facing]), + 1.0f, + 1.0f, + 1.0f, + light, + OverlayTexture.DEFAULT_UV + ) + matrices.pop() + } + + matrices.pop() + } + + companion object { + private val STATES_ID = Identifier.of(PirateRadio.MOD_ID, "disposable-transmitter") + private val MODEL_ID = mapOf( + Direction.DOWN to ModelIdentifier(STATES_ID, "facing=down"), + Direction.UP to ModelIdentifier(STATES_ID, "facing=up"), + Direction.NORTH to ModelIdentifier(STATES_ID, "facing=north"), + Direction.SOUTH to ModelIdentifier(STATES_ID, "facing=south"), + Direction.WEST to ModelIdentifier(STATES_ID, "facing=west"), + Direction.EAST to ModelIdentifier(STATES_ID, "facing=east"), + ) + } +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/entity/ElectronicsTraderEntityRenderer.kt b/src/client/kotlin/space/autistic/radio/client/entity/ElectronicsTraderEntityRenderer.kt index 91c29db..5da8e17 100644 --- a/src/client/kotlin/space/autistic/radio/client/entity/ElectronicsTraderEntityRenderer.kt +++ b/src/client/kotlin/space/autistic/radio/client/entity/ElectronicsTraderEntityRenderer.kt @@ -16,7 +16,7 @@ class ElectronicsTraderEntityRenderer(context: EntityRendererFactory.Context) : ) { companion object { - val TEXTURE = Identifier.of(PirateRadio.MOD_ID, "electronics-trader") + val TEXTURE = Identifier.of(PirateRadio.MOD_ID, "textures/entity/electronics-trader.png") } override fun getTexture(entity: ElectronicsTraderEntity?): Identifier = TEXTURE diff --git a/src/client/kotlin/space/autistic/radio/client/fmsim/FmSimulatorMode.kt b/src/client/kotlin/space/autistic/radio/client/fmsim/FmSimulatorMode.kt new file mode 100644 index 0000000..a8bc9fd --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/fmsim/FmSimulatorMode.kt @@ -0,0 +1,7 @@ +package space.autistic.radio.client.fmsim + +enum class FmSimulatorMode { + FULL, + FAST, + DEAF +} diff --git a/src/client/kotlin/space/autistic/radio/client/gui/FmReceiverScreen.kt b/src/client/kotlin/space/autistic/radio/client/gui/FmReceiverScreen.kt index 4bd4db2..f5fd729 100644 --- a/src/client/kotlin/space/autistic/radio/client/gui/FmReceiverScreen.kt +++ b/src/client/kotlin/space/autistic/radio/client/gui/FmReceiverScreen.kt @@ -1,11 +1,634 @@ package space.autistic.radio.client.gui +import com.dylibso.chicory.experimental.aot.AotMachineFactory +import com.dylibso.chicory.runtime.* +import com.dylibso.chicory.wasm.ChicoryException +import com.dylibso.chicory.wasm.InvalidException +import com.dylibso.chicory.wasm.Parser +import com.dylibso.chicory.wasm.types.FunctionType +import com.dylibso.chicory.wasm.types.Value +import com.dylibso.chicory.wasm.types.ValueType +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.gui.widget.ClickableWidget +import net.minecraft.client.render.GameRenderer +import net.minecraft.client.util.InputUtil +import net.minecraft.text.StringVisitable import net.minecraft.text.Text +import net.minecraft.util.Colors +import net.minecraft.util.Identifier +import org.lwjgl.glfw.GLFW +import org.slf4j.LoggerFactory +import org.slf4j.event.Level +import space.autistic.radio.PirateRadio +import space.autistic.radio.PirateRadio.logger +import space.autistic.radio.client.PirateRadioClient +import space.autistic.radio.client.fmsim.FmSimulatorMode +import space.autistic.radio.wasm.Bindings.Companion.bindFunc +import java.lang.invoke.MethodHandles +import kotlin.math.max +import kotlin.reflect.jvm.javaMethod + class FmReceiverScreen : Screen(Text.translatable("pirate-radio.fm-receiver")) { + private var loggingEventBuilder = wasmLogger.makeLoggingEventBuilder(Level.INFO) + private var instance: Instance? = null + private var failure: Exception? = null + set(value) { + field = value + clearChildren() + } + private var packTitle: Text? = null + + private var backgroundWidth = 0 + private var backgroundHeight = 0 + private var backgroundTextureWidth = 256 + private var backgroundTextureHeight = 256 + + private var drawContext: DrawContext? = null + + private var textObjects = ArrayList() + private var textObjectsFree = ArrayList() + + private val frequencyPlusWidget = ButtonWidget.builder(Text.translatable("pirate-radio.frequency.plus")) { + if (InputUtil.isKeyPressed(MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_LEFT_SHIFT) + || InputUtil.isKeyPressed(MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_RIGHT_SHIFT) + ) { + PirateRadioClient.frequency += 10 + } else { + PirateRadioClient.frequency++ + } + }.dimensions(0, 0, 20, 20) + .narrationSupplier { ButtonWidget.getNarrationMessage(Text.translatable("pirate-radio.frequency.plus.narrated")) } + .build() + private val frequencyMinusWidget = ButtonWidget.builder(Text.translatable("pirate-radio.frequency.minus")) { + if (InputUtil.isKeyPressed(MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_LEFT_SHIFT) + || InputUtil.isKeyPressed(MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_RIGHT_SHIFT) + ) { + PirateRadioClient.frequency -= 10 + } else { + PirateRadioClient.frequency-- + } + }.dimensions(20, 0, 20, 20) + .narrationSupplier { ButtonWidget.getNarrationMessage(Text.translatable("pirate-radio.frequency.plus.narrated")) } + .build() + + private val volumePlusWidget = ButtonWidget.builder(Text.translatable("pirate-radio.volume.plus")) { + PirateRadioClient.volume++ + }.dimensions(0, 20, 20, 20) + .narrationSupplier { ButtonWidget.getNarrationMessage(Text.translatable("pirate-radio.volume.plus.narrated")) } + .build() + private val volumeMinusWidget = ButtonWidget.builder(Text.translatable("pirate-radio.volume.minus")) { + PirateRadioClient.volume-- + }.dimensions(20, 20, 20, 20) + .narrationSupplier { ButtonWidget.getNarrationMessage(Text.translatable("pirate-radio.volume.minus.narrated")) } + .build() + private val toggleModes = ButtonWidget.builder(Text.translatable("pirate-radio.mode")) { + PirateRadioClient.mode = when (PirateRadioClient.mode) { + FmSimulatorMode.FULL -> FmSimulatorMode.FAST + else -> FmSimulatorMode.FULL + } + }.position(0, 40).build() + override fun init() { - // TODO + if (failure == null && instance == null) { + try { + setupWasm() + } catch (e: WasmScreenException) { + logger.error("Failed to setup wasm.", e) + failure = e + } + } + if (failure == null) { + try { + instance!!.export("init").apply() + addDrawableChild(frequencyPlusWidget) + addDrawableChild(frequencyMinusWidget) + addDrawableChild(volumePlusWidget) + addDrawableChild(volumeMinusWidget) + addDrawableChild(toggleModes) + volumePlusWidget.visible + } catch (e: ChicoryException) { + failure = WasmScreenException("Skin failed to initialize", e) + logger.error("Failed to initialize.", failure) + } catch (e: WasmScreenException) { + failure = e + logger.error("Failed to initialize.", failure) + } + } + } + + override fun renderBackground(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.renderInGameBackground(context) + + if (failure == null) { + if (backgroundWidth or backgroundHeight != 0) { + RenderSystem.setShader(GameRenderer::getPositionTexProgram) + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f) + RenderSystem.setShaderTexture(0, TEXTURE) + val x = (width - backgroundWidth) / 2 + val y = (height - backgroundHeight) / 2 + context.drawTexture( + TEXTURE, + x, + y, + 0f, + 0f, + backgroundWidth, + backgroundHeight, + backgroundTextureWidth, + backgroundTextureHeight + ) + } + } + + + if (client!!.debugHud.shouldShowDebugHud()) { + context.drawText( + textRenderer, + Text.translatable("pirate-radio.skin-pack", packTitle), + 0, + height - textRenderer.fontHeight, + Colors.WHITE, + true + ) + } + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + + if (failure != null) { + context.drawTextWrapped( + textRenderer, StringVisitable.plain(failure!!.message), (width - 320) / 2, 0, 320, Colors.WHITE + ) + } else { + try { + drawContext = context + instance!!.export("render").apply(mouseX.toLong(), mouseY.toLong(), Value.floatToLong(delta)) + } catch (e: ChicoryException) { + failure = WasmScreenException("Skin failed to initialize", e) + logger.error("Failed to initialize.", failure) + } catch (e: WasmScreenException) { + failure = e + logger.error("Failed to initialize.", failure) + } finally { + drawContext = null + } + } + } + + override fun shouldPause() = false + + private fun loggerLog() { + loggingEventBuilder.log() + } + + private fun loggerLogMessage(message: String) { + loggingEventBuilder.log(message) + } + + private fun loggerBegin(level: Int) { + loggingEventBuilder = wasmLogger.makeLoggingEventBuilder( + try { + Level.intToLevel(level) + } catch (e: IllegalArgumentException) { + Level.INFO + } + ) + } + + private fun loggerSetMessage(message: String) { + loggingEventBuilder.setMessage(message) + } + + private fun loggerAddArgumentString(arg: String) { + loggingEventBuilder.addArgument(arg) + } + + private fun loggerAddArgumentInt(arg: Int) { + loggingEventBuilder.addArgument(arg) + } + + private fun loggerAddArgumentLong(arg: Long) { + loggingEventBuilder.addArgument(arg) + } + + private fun loggerAddArgumentFloat(arg: Float) { + loggingEventBuilder.addArgument(arg) + } + + private fun loggerAddArgumentDouble(arg: Double) { + loggingEventBuilder.addArgument(arg) + } + + private fun setBackgroundSize(width: Int, height: Int) { + this.backgroundWidth = max(width, 0) + this.backgroundHeight = max(height, 0) + } + + private fun setBackgroundTextureSize(width: Int, height: Int) { + this.backgroundTextureWidth = max(width, 0) + this.backgroundTextureHeight = max(height, 0) + } + + private fun renderTextTranslatable(text: String, x: Int, y: Int, color: Int, shadow: Boolean) { + drawContext?.drawText(textRenderer, Text.translatable(text), x, y, color, shadow) + } + + private fun renderTextLiteral(text: String, x: Int, y: Int, color: Int, shadow: Boolean) { + drawContext?.drawText(textRenderer, Text.literal(text), x, y, color, shadow) + } + + private fun renderTextObject(handle: Int, x: Int, y: Int, color: Int, shadow: Boolean) { + if (handle >= 0 && handle < textObjects.size && textObjects[handle] != null) { + drawContext?.drawText(textRenderer, textObjects[handle], x, y, color, shadow) + } else { + throw WasmScreenException("Invalid text handle: $handle", null) + } + } + + private fun frequencyPlusWidgetDimensions(x: Int, y: Int, width: Int, height: Int) { + frequencyPlusWidget.x = x + frequencyPlusWidget.y = y + frequencyPlusWidget.width = width + frequencyPlusWidget.height = height + } + + private fun frequencyMinusWidgetDimensions(x: Int, y: Int, width: Int, height: Int) { + frequencyMinusWidget.x = x + frequencyMinusWidget.y = y + frequencyMinusWidget.width = width + frequencyMinusWidget.height = height + } + + private fun volumePlusWidgetDimensions(x: Int, y: Int, width: Int, height: Int) { + volumePlusWidget.x = x + volumePlusWidget.y = y + volumePlusWidget.width = width + volumePlusWidget.height = height + } + + private fun volumeMinusWidgetDimensions(x: Int, y: Int, width: Int, height: Int) { + volumeMinusWidget.x = x + volumeMinusWidget.y = y + volumeMinusWidget.width = width + volumeMinusWidget.height = height + } + + private fun toggleWidgetDimensions(x: Int, y: Int, width: Int, height: Int) { + toggleModes.x = x + toggleModes.y = y + toggleModes.width = width + toggleModes.height = height + } + + private fun getFrequency(): Int = PirateRadioClient.frequency + + private fun getMode(): Int = PirateRadioClient.mode.ordinal + + private fun getStereo(): Boolean = PirateRadioClient.stereo + + private fun getVolume(): Int = PirateRadioClient.volume + + private fun getStereoPilot(): Float = 0f + + private fun getWidth(): Int = width + + private fun getHeight(): Int = height + + // useful for drawing the stereo pilot, not that it's implemented + private fun drawImage( + red: Float, + green: Float, + blue: Float, + alpha: Float, + x: Int, + y: Int, + u: Float, + v: Float, + w: Int, + h: Int + ) { + val drawContext = drawContext ?: throw WasmScreenException("Inappropriate draw call", null) + RenderSystem.setShader(GameRenderer::getPositionTexProgram) + RenderSystem.setShaderColor(red, green, blue, alpha) + RenderSystem.setShaderTexture(0, TEXTURE) + drawContext.drawTexture( + TEXTURE, + x, + y, + u, + v, + w, + h, + backgroundTextureWidth, + backgroundTextureHeight + ) + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + } + + private fun textFree(handle: Int) { + if (handle >= 0 && handle < textObjects.size && textObjects[handle] != null) { + textObjects[handle] = null + textObjectsFree.add(handle) + } else { + throw WasmScreenException("Invalid text handle: $handle", null) + } + } + + private fun textLiteral(text: String): Int { + val index = if (textObjectsFree.isNotEmpty()) { + textObjectsFree.removeLast() + } else { + textObjects.add(null) + textObjects.size - 1 + } + textObjects[index] = Text.literal(text) + return index + } + + private fun textTranslatable(text: String): Int { + val index = if (textObjectsFree.isNotEmpty()) { + textObjectsFree.removeLast() + } else { + textObjects.add(null) + textObjects.size - 1 + } + textObjects[index] = Text.translatable(text) + return index + } + + private fun textTranslatableArguments(text: String, args: List): Int { + args.forEach { handle -> + if (handle < 0 || handle >= textObjects.size || textObjects[handle] == null) { + throw WasmScreenException("Invalid text handle: $handle", null) + } + } + val index = if (textObjectsFree.isNotEmpty()) { + textObjectsFree.removeLast() + } else { + textObjects.add(null) + textObjects.size - 1 + } + textObjects[index] = Text.translatable(text, *Array(args.size) { textObjects[args[it]] }) + return index + } + + private fun setupWasm() { + // this should never throw + val resource = this.client!!.resourceManager.getResourceOrThrow(WASM_GUI) + packTitle = resource.pack.info.title + val module = try { + Parser.parse(resource.inputStream) + } catch (e: ChicoryException) { + throw WasmScreenException("Skin failed to load: error parsing module", e) + } + val importValues = ImportValues.builder() + importValues.addFunction( + bindFunc( + "text", "free", lookup, this::textFree.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "text", "literal", lookup, this::textLiteral.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "text", "translatable", lookup, this::textTranslatable.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "text", "translatable-arguments", lookup, this::textTranslatableArguments.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "frequency-plus-widget", + "set-dimensions", + lookup, + this::frequencyPlusWidgetDimensions.javaMethod!!, + this + ) + ) + importValues.addFunction( + bindFunc( + "frequency-minus-widget", + "set-dimensions", + lookup, + this::frequencyMinusWidgetDimensions.javaMethod!!, + this + ) + ) + importValues.addFunction( + bindFunc( + "volume-plus-widget", "set-dimensions", lookup, this::volumePlusWidgetDimensions.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "volume-minus-widget", "set-dimensions", lookup, this::volumeMinusWidgetDimensions.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "toggle-widget", "set-dimensions", lookup, this::toggleWidgetDimensions.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "simulator", "get-frequency", lookup, this::getFrequency.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "simulator", "get-volume", lookup, this::getVolume.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "simulator", "get-mode", lookup, this::getMode.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "simulator", "get-stereo", lookup, this::getStereo.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "simulator", "get-stereo-pilot", lookup, this::getStereoPilot.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "get-width", lookup, this::getWidth.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "get-height", lookup, this::getHeight.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "draw-image", lookup, this::drawImage.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "set-background-size", lookup, this::setBackgroundSize.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "set-background-texture-size", lookup, this::setBackgroundTextureSize.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "render-text-translatable", lookup, this::renderTextTranslatable.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "render-text-literal", lookup, this::renderTextLiteral.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "render-text-object", lookup, this::renderTextObject.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "screen", "set-background-size", lookup, this::setBackgroundSize.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "log", lookup, this::loggerLog.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "log-message", lookup, this::loggerLogMessage.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "begin", lookup, this::loggerBegin.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "log-message", lookup, this::loggerSetMessage.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "add-argument-string", lookup, this::loggerAddArgumentString.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "add-argument-int", lookup, this::loggerAddArgumentInt.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "add-argument-long", lookup, this::loggerAddArgumentLong.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "add-argument-float", lookup, this::loggerAddArgumentFloat.javaMethod!!, this + ) + ) + importValues.addFunction( + bindFunc( + "logger", "add-argument-double", lookup, this::loggerAddArgumentDouble.javaMethod!!, this + ) + ) + val builder = Instance.builder(module) + builder.withMachineFactory(AotMachineFactory(module)) + builder.withImportValues(importValues.build()) + val instance = try { + builder.build() + } catch (e: ChicoryException) { + throw WasmScreenException("Skin failed to load: error constructing module", e) + } + var initialize: ExportFunction? = null + try { + initialize = instance.export("_initialize") + } catch (_: InvalidException) { + // export may not exist, it's fine + } catch (e: ChicoryException) { + throw WasmScreenException("Skin failed to load: error initializing module", e) + } + try { + initialize?.apply() + } catch (e: ChicoryException) { + throw WasmScreenException("Skin failed to load: error initializing module", e) + } + try { + checkFuncExports( + instance, + "init" to FunctionType.empty(), + "render" to FunctionType.of(arrayOf(ValueType.I32, ValueType.I32, ValueType.F32), emptyArray()) + ) + } catch (e: ChicoryException) { + throw WasmScreenException("Skin failed to load: error checking exports", e) + } + this.instance = instance + } + + private class WasmScreenException(message: String?, cause: Throwable?) : Exception(message, cause) + + @Throws(WasmScreenException::class) + private fun checkFuncExports(instance: Instance, vararg exports: Pair) { + val sb = StringBuilder() + exports.forEach { (name, type) -> + when (exportStatus(instance, name, type)) { + ExportStatus.MISSING -> sb.append("missing: ", name, type, "\n") + ExportStatus.TYPE_MISMATCH -> sb.append("type mismatch: ", name, type, "\n") + ExportStatus.OK -> {} + } + } + if (sb.isNotEmpty()) { + sb.setLength(sb.length - 1) + throw WasmScreenException("Skin failed to load: error checking exports:\n$sb", null) + } + } + + private enum class ExportStatus { + OK, MISSING, TYPE_MISMATCH, + } + + private fun exportStatus(instance: Instance, name: String, type: FunctionType): ExportStatus { + try { + // because instance.exportType doesn't check if it's actually a function + instance.export(name) + return if (instance.exportType(name) == type) ExportStatus.OK else ExportStatus.TYPE_MISMATCH + } catch (_: InvalidException) { + return ExportStatus.MISSING + } + } + + companion object { + private val WASM_GUI = Identifier.of(PirateRadio.MOD_ID, "guis/radio-receiver.wasm") + + // the texture is at a fixed location but the respack can replace it too + private val TEXTURE = Identifier.of(PirateRadio.MOD_ID, "textures/gui/radio-receiver.png") + + private val wasmLogger = LoggerFactory.getLogger(PirateRadio.MOD_ID + "/wasm/radio-receiver") + private val lookup = MethodHandles.lookup() } -} \ No newline at end of file +} diff --git a/src/client/kotlin/space/autistic/radio/client/gui/StorageCardEditScreen.kt b/src/client/kotlin/space/autistic/radio/client/gui/StorageCardEditScreen.kt new file mode 100644 index 0000000..a1dd5d5 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/gui/StorageCardEditScreen.kt @@ -0,0 +1,115 @@ +package space.autistic.radio.client.gui + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.gui.widget.EditBoxWidget +import net.minecraft.client.gui.widget.TextFieldWidget +import net.minecraft.client.util.NarratorManager +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.ItemStack +import net.minecraft.screen.ScreenTexts +import net.minecraft.text.Text +import net.minecraft.util.Colors +import net.minecraft.util.Hand +import space.autistic.radio.PirateRadioComponents +import space.autistic.radio.network.StorageCardUpdateC2SPayload + +class StorageCardEditScreen( + private val player: PlayerEntity, private val storageCard: ItemStack, private val hand: Hand +) : Screen(NarratorManager.EMPTY) { + private var dirty = false + private var frequency = 768 + private lateinit var editBox: EditBoxWidget + private lateinit var frequencyBox: TextFieldWidget + private lateinit var doneButton: ButtonWidget + + override fun init() { + if (!this::editBox.isInitialized) { + editBox = EditBoxWidget( + textRenderer, + (width - WIDTH) / 2, + (height - HEIGHT) / 2, + WIDTH, + HEIGHT, + Text.translatable("pirate-radio.message"), + Text.translatable("pirate-radio.message") + ) + editBox.text = storageCard.get(PirateRadioComponents.MESSAGE)?.literalString ?: "" + editBox.setMaxLength(16384) + editBox.setChangeListener { + dirty = true + } + } + frequency = storageCard.getOrDefault(PirateRadioComponents.FREQUENCY, 768) + if (!this::frequencyBox.isInitialized) { + frequencyBox = + TextFieldWidget(textRenderer, FREQ_WIDTH, FREQ_HEIGHT, Text.translatable("pirate-radio.frequency.edit")) + frequencyBox.setMaxLength(5) + frequencyBox.text = (frequency / 10).toString() + "." + (frequency % 10).toString() + frequencyBox.setTextPredicate { + FREQ_REGEX_CHARACTERS.matches(it) + } + frequencyBox.setChangedListener { + if (FREQ_REGEX.matches(it)) { + frequency = it.replace(".", "").toInt() + frequencyBox.setEditableColor(Colors.WHITE) + dirty = true + } else { + frequencyBox.setEditableColor(Colors.RED) + } + } + } + editBox.x = (width - WIDTH) / 2 + editBox.y = (height - HEIGHT) / 2 + frequencyBox.x = editBox.x + frequencyBox.y = editBox.y - FREQ_HEIGHT + addDrawableChild(frequencyBox) + addDrawableChild(editBox) + doneButton = this.addDrawableChild( + ButtonWidget.builder(ScreenTexts.DONE) { + client!!.setScreen(null) + this.saveChanges() + }.dimensions(editBox.x + editBox.width - 98, frequencyBox.y, 98, 20).build() + ) + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + + context.drawText( + textRenderer, + Text.translatable("pirate-radio.frequency.edit"), + frequencyBox.x, + frequencyBox.y - textRenderer.fontHeight, + Colors.WHITE, + true + ) + } + + private fun saveChanges() { + if (this.dirty) { + this.writeNbtData() + val slot = if (this.hand == Hand.MAIN_HAND) player.inventory.selectedSlot else 40 + ClientPlayNetworking.send(StorageCardUpdateC2SPayload(slot, this.editBox.text, this.frequency)) + } + } + + private fun writeNbtData() { + this.storageCard.set(PirateRadioComponents.MESSAGE, Text.literal(this.editBox.text)) + this.storageCard.set(PirateRadioComponents.FREQUENCY, this.frequency) + } + + companion object { + const val WIDTH = 192 + const val HEIGHT = 144 + + const val FREQ_WIDTH = 40 + const val FREQ_HEIGHT = 20 + + val FREQ_REGEX = Regex("^76\\.[8-9]|7[7-9]\\.[0-9]|[8-9][0-9]\\.[0-9]|10[0-7]\\.[0-9]|108\\.0$") + val FREQ_REGEX_CHARACTERS = Regex("^[0-9]*\\.?[0-9]?$") + + } +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt new file mode 100644 index 0000000..b69cc42 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt @@ -0,0 +1,68 @@ +package space.autistic.radio.client.sound + +import net.fabricmc.fabric.api.client.sound.v1.FabricSoundInstance +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.client.sound.* +import net.minecraft.sound.SoundCategory +import net.minecraft.sound.SoundEvents +import net.minecraft.util.Identifier +import space.autistic.radio.PirateRadioEntityTypes +import space.autistic.radio.client.PirateRadioClient +import space.autistic.radio.entity.DisposableTransmitterEntity +import java.util.concurrent.CompletableFuture + +class PirateRadioSoundInstance(private val player: ClientPlayerEntity) : MovingSoundInstance( + SoundEvents.INTENTIONALLY_EMPTY, SoundCategory.MUSIC, SoundInstance.createRandom() +) { + + init { + this.repeat = false + this.repeatDelay = 0 + this.volume = 1f + this.pitch = 1f + this.relative = true + } + + override fun tick() { + if (player.isRemoved) { + this.setDone() + return + } + @Suppress("UNCHECKED_CAST") val trackedEntities: List = + player.clientWorld.entities.filter { it.type == PirateRadioEntityTypes.DISPOSABLE_TRANSMITTER } + .filter { (it as DisposableTransmitterEntity).frequency <= PirateRadioClient.frequency + 1 && it.frequency >= PirateRadioClient.frequency - 1 } + .sortedByDescending { player.pos.squaredDistanceTo(it.pos) } as List + val main = trackedEntities.filter { it.frequency == PirateRadioClient.frequency }.take(2) + val lower = trackedEntities.find { it.frequency == PirateRadioClient.frequency - 1 } + val upper = trackedEntities.find { it.frequency == PirateRadioClient.frequency + 1 } + // TODO implement + } + + override fun getAudioStream( + loader: SoundLoader?, id: Identifier?, repeatInstantly: Boolean + ): CompletableFuture { + // TODO setup thread + return CompletableFuture.completedFuture(ReceiverAudioStream) + } + + override fun getVolume(): Float { + return this.volume + } + + override fun getPitch(): Float { + return this.pitch + } + + override fun getSound(): Sound { + return Sound( + FabricSoundInstance.EMPTY_SOUND, + { 1f }, + { 1f }, + 1, + Sound.RegistrationType.SOUND_EVENT, + true, + false, + 16 + ) + } +} diff --git a/src/client/kotlin/space/autistic/radio/client/sound/ReceiverAudioStream.kt b/src/client/kotlin/space/autistic/radio/client/sound/ReceiverAudioStream.kt new file mode 100644 index 0000000..5ca802b --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/sound/ReceiverAudioStream.kt @@ -0,0 +1,34 @@ +package space.autistic.radio.client.sound + +import it.unimi.dsi.fastutil.floats.FloatConsumer +import net.minecraft.client.sound.AudioStream +import net.minecraft.client.sound.ChannelList +import java.nio.ByteBuffer +import javax.sound.sampled.AudioFormat + +object ReceiverAudioStream : AudioStream { + private val format = AudioFormat(48000f, 16, 2, true, false) + + override fun close() { + // TODO, nop for now, should stop the processing + } + + override fun getFormat(): AudioFormat { + return format + } + + override fun read(size: Int): ByteBuffer { + val channelList = ChannelList(size + 8192) + + while (this.read(channelList) && channelList.currentBufferSize < size) { + } + + return channelList.buffer + } + + private fun read(channelList: FloatConsumer): Boolean { + channelList.accept(0f) + channelList.accept(0f) + return true + } +} \ No newline at end of file diff --git a/src/main/generated/.cache/4145a4ade350d062a154f42d7ad0d98fb52bf04b b/src/main/generated/.cache/4145a4ade350d062a154f42d7ad0d98fb52bf04b index 072c021..d246b4f 100644 --- a/src/main/generated/.cache/4145a4ade350d062a154f42d7ad0d98fb52bf04b +++ b/src/main/generated/.cache/4145a4ade350d062a154f42d7ad0d98fb52bf04b @@ -1,3 +1,3 @@ -// 1.21.1 2025-02-09T00:02:42.294183715 Pirate Radio/Recipes -84f8cd2b2d9d1afcf2a5cf000905c264a6d8267c data/pirate-radio/recipe/disposable-transmitter.json +// 1.21.1 2025-03-14T18:16:59.621431904 Pirate Radio/Recipes +368c94ec69c8320836c81014b1cfeab0742cb6e8 data/pirate-radio/recipe/disposable-transmitter.json 86e73a1d034dc407ce65e0e61af19b1db43e1939 data/pirate-radio/advancement/recipes/misc/disposable-transmitter.json diff --git a/src/main/generated/.cache/bd1ee27e4c10ec669c0e0894b64dd83a58902c72 b/src/main/generated/.cache/bd1ee27e4c10ec669c0e0894b64dd83a58902c72 index cf1f8c7..8e7e12b 100644 --- a/src/main/generated/.cache/bd1ee27e4c10ec669c0e0894b64dd83a58902c72 +++ b/src/main/generated/.cache/bd1ee27e4c10ec669c0e0894b64dd83a58902c72 @@ -1,5 +1,4 @@ -// 1.21.1 2025-02-09T00:02:42.294917543 Pirate Radio/Model Definitions -3507512497435bf1047ebd71ae1f4881ceb67f44 assets/pirate-radio/models/item/fm-receiver.json +// 1.21.1 2025-03-14T18:16:59.623037325 Pirate Radio/Model Definitions ab60b602066c94b5746065e1b139a383a6c26429 assets/pirate-radio/models/item/powerbank.json fb8af1b0939020c3a89a7736e47d9f688b38a2c9 assets/pirate-radio/models/item/storage-card.json dbc04d664dacd99a76580bcff2c5b944abb0730e assets/pirate-radio/models/item/sbc.json diff --git a/src/main/generated/assets/pirate-radio/models/item/fm-receiver.json b/src/main/generated/assets/pirate-radio/models/item/fm-receiver.json deleted file mode 100644 index 71813c4..0000000 --- a/src/main/generated/assets/pirate-radio/models/item/fm-receiver.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "minecraft:item/generated", - "textures": { - "layer0": "pirate-radio:item/fm-receiver" - } -} \ No newline at end of file diff --git a/src/main/generated/data/pirate-radio/recipe/disposable-transmitter.json b/src/main/generated/data/pirate-radio/recipe/disposable-transmitter.json index 2a1d645..87b9be6 100644 --- a/src/main/generated/data/pirate-radio/recipe/disposable-transmitter.json +++ b/src/main/generated/data/pirate-radio/recipe/disposable-transmitter.json @@ -10,9 +10,6 @@ }, { "item": "pirate-radio:powerbank" - }, - { - "item": "pirate-radio:storage-card" } ], "result": { diff --git a/src/main/kotlin/space/autistic/radio/CommonProxy.kt b/src/main/kotlin/space/autistic/radio/CommonProxy.kt new file mode 100644 index 0000000..76a4b22 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/CommonProxy.kt @@ -0,0 +1,10 @@ +package space.autistic.radio + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.ItemStack +import net.minecraft.util.Hand + +open class CommonProxy : SidedProxy { + override fun useStorageCard(player: PlayerEntity, item: ItemStack, hand: Hand) { + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/PirateRadio.kt b/src/main/kotlin/space/autistic/radio/PirateRadio.kt index 54d0b9f..d463e41 100644 --- a/src/main/kotlin/space/autistic/radio/PirateRadio.kt +++ b/src/main/kotlin/space/autistic/radio/PirateRadio.kt @@ -1,17 +1,58 @@ package space.autistic.radio +import com.mojang.serialization.Codec import net.fabricmc.api.ModInitializer +import net.fabricmc.fabric.api.event.registry.DynamicRegistries +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking +import net.minecraft.item.ItemStack +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.text.Text +import net.minecraft.util.Identifier import org.slf4j.LoggerFactory +import space.autistic.radio.antenna.Antenna +import space.autistic.radio.antenna.PirateRadioAntennaSerializers +import space.autistic.radio.network.StorageCardUpdateC2SPayload +import kotlin.math.max +import kotlin.math.min object PirateRadio : ModInitializer { - const val MOD_ID = "pirate-radio" - private val logger = LoggerFactory.getLogger(MOD_ID) + var proxy: SidedProxy? = null + const val MOD_ID = "pirate-radio" + val logger = LoggerFactory.getLogger(MOD_ID) - override fun onInitialize() { - logger.info("This project is made with love by a queer trans person.\n" + - "The folks of these identities who have contributed to the project would like to make their identities known:\n" + - "Autgender; Not a person; Anticapitalist; Genderqueer; Trans.") - PirateRadioItems.initialize() - PirateRadioEntityTypes.initialize() - } + + override fun onInitialize() { + if (proxy == null) { + proxy = CommonProxy() + } + logger.info( + "This project is made with love by a queer trans person.\n" + + "The folks of these identities who have contributed to the project would like to make their identities known:\n" + + "Autgender; Not a person; Anticapitalist; Genderqueer; Trans." + ) + PirateRadioRegistries.initialize() + PirateRadioAntennaSerializers.initialize() + DynamicRegistries.registerSynced(PirateRadioRegistryKeys.ANTENNA, Antenna.CODEC) + PayloadTypeRegistry.playC2S() + .register(StorageCardUpdateC2SPayload.PAYLOAD_ID, StorageCardUpdateC2SPayload.CODEC) + ServerPlayNetworking.registerGlobalReceiver(StorageCardUpdateC2SPayload.PAYLOAD_ID) { payload, context -> + if (!context.player().isAlive) { + return@registerGlobalReceiver + } + val stack = context.player().inventory.getStack(payload.slot) + if (stack.isOf(PirateRadioItems.STORAGE_CARD)) { + stack.set(PirateRadioComponents.FREQUENCY, min(1080, max(768, payload.frequency))) + var text = payload.text + if (text.length > 16384) { + text = text.substring(0, 16384) + } + stack.set(PirateRadioComponents.MESSAGE, Text.literal(text)) + } + } + PirateRadioComponents.initialize() + PirateRadioItems.initialize() + PirateRadioEntityTypes.initialize() + } } \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/PirateRadioComponents.kt b/src/main/kotlin/space/autistic/radio/PirateRadioComponents.kt new file mode 100644 index 0000000..f79f26a --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/PirateRadioComponents.kt @@ -0,0 +1,30 @@ +package space.autistic.radio + +import net.minecraft.component.ComponentType +import net.minecraft.network.codec.PacketCodecs +import net.minecraft.registry.Registries +import net.minecraft.registry.Registry +import net.minecraft.text.Text +import net.minecraft.text.TextCodecs +import net.minecraft.util.Identifier +import net.minecraft.util.dynamic.Codecs + +object PirateRadioComponents { + val FREQUENCY = Registry.register( + Registries.DATA_COMPONENT_TYPE, + Identifier.of(PirateRadio.MOD_ID, "frequency"), + ComponentType.builder().codec( + Codecs.rangedInt(768, 1080) + ).packetCodec(PacketCodecs.VAR_INT).build() + ) + + val MESSAGE = Registry.register( + Registries.DATA_COMPONENT_TYPE, + Identifier.of(PirateRadio.MOD_ID, "message"), + ComponentType.builder().codec(TextCodecs.STRINGIFIED_CODEC).packetCodec(TextCodecs.REGISTRY_PACKET_CODEC) + .cache().build() + ) + + fun initialize() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/PirateRadioEntityTypes.kt b/src/main/kotlin/space/autistic/radio/PirateRadioEntityTypes.kt index f147394..3fbb34f 100644 --- a/src/main/kotlin/space/autistic/radio/PirateRadioEntityTypes.kt +++ b/src/main/kotlin/space/autistic/radio/PirateRadioEntityTypes.kt @@ -11,13 +11,31 @@ import net.minecraft.registry.RegistryKey import net.minecraft.registry.RegistryKeys import net.minecraft.util.Identifier import space.autistic.radio.entity.ElectronicsTraderEntity +import space.autistic.radio.entity.DisposableTransmitterEntity object PirateRadioEntityTypes { - val ELECTRONICS_TRADER_KEY = RegistryKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(PirateRadio.MOD_ID, "electronics-trader")) - val ELECTRONICS_TRADER = register(EntityType.Builder.create(::ElectronicsTraderEntity, SpawnGroup.MISC).dimensions(0.6F, 1.95F).eyeHeight(1.62F).maxTrackingRange(10), ELECTRONICS_TRADER_KEY) + val ELECTRONICS_TRADER_KEY = + RegistryKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(PirateRadio.MOD_ID, "electronics-trader")) + val ELECTRONICS_TRADER = register( + EntityType.Builder.create(::ElectronicsTraderEntity, SpawnGroup.MISC).dimensions(0.6F, 1.95F).eyeHeight(1.62F) + .maxTrackingRange(10), ELECTRONICS_TRADER_KEY + ) - fun register(entityTypeBuilder: EntityType.Builder, registryKey: RegistryKey>): EntityType { - return Registry.register(Registries.ENTITY_TYPE, registryKey.value, entityTypeBuilder.build(registryKey.value.path)) + val DISPOSABLE_TRANSMITTER_KEY = RegistryKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(PirateRadio.MOD_ID, "disposable-transmitter")) + val DISPOSABLE_TRANSMITTER = register( + EntityType.Builder.create(::DisposableTransmitterEntity, SpawnGroup.MISC).dimensions(0.5F, 0.5F).eyeHeight(0F) + .maxTrackingRange(10).trackingTickInterval(Integer.MAX_VALUE), DISPOSABLE_TRANSMITTER_KEY + ) + + fun register( + entityTypeBuilder: EntityType.Builder, + registryKey: RegistryKey> + ): EntityType { + return Registry.register( + Registries.ENTITY_TYPE, + registryKey.value, + entityTypeBuilder.build(registryKey.value.path) + ) } fun initialize() { diff --git a/src/main/kotlin/space/autistic/radio/PirateRadioItems.kt b/src/main/kotlin/space/autistic/radio/PirateRadioItems.kt index 490acaf..e00e4e6 100644 --- a/src/main/kotlin/space/autistic/radio/PirateRadioItems.kt +++ b/src/main/kotlin/space/autistic/radio/PirateRadioItems.kt @@ -2,12 +2,16 @@ package space.autistic.radio import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents import net.minecraft.item.Item +import net.minecraft.item.ItemFrameItem import net.minecraft.item.ItemGroups import net.minecraft.registry.Registries import net.minecraft.registry.Registry import net.minecraft.registry.RegistryKey import net.minecraft.registry.RegistryKeys +import net.minecraft.text.Text import net.minecraft.util.Identifier +import space.autistic.radio.item.DisposableTransmitterItem +import space.autistic.radio.item.StorageCardItem object PirateRadioItems { val SBC_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "sbc")) @@ -17,11 +21,20 @@ object PirateRadioItems { val POWERBANK_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "powerbank")) val POWERBANK = register(Item(Item.Settings()), POWERBANK_KEY) val STORAGE_CARD_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "storage-card")) - val STORAGE_CARD = register(Item(Item.Settings()), STORAGE_CARD_KEY) - val DISPOSABLE_TRANSMITTER_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "disposable-transmitter")) - val DISPOSABLE_TRANSMITTER = register(Item(Item.Settings()), DISPOSABLE_TRANSMITTER_KEY) - val FM_RECEIVER_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "fm-receiver")) - val FM_RECEIVER = register(Item(Item.Settings()), FM_RECEIVER_KEY) + val STORAGE_CARD = register( + StorageCardItem( + Item.Settings().maxCount(1).component(PirateRadioComponents.FREQUENCY, 768) + .component(PirateRadioComponents.MESSAGE, Text.literal("")) + ), STORAGE_CARD_KEY + ) + val DISPOSABLE_TRANSMITTER_KEY = + RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "disposable-transmitter")) + val DISPOSABLE_TRANSMITTER = register( + DisposableTransmitterItem(PirateRadioEntityTypes.DISPOSABLE_TRANSMITTER, Item.Settings()), + DISPOSABLE_TRANSMITTER_KEY + ) +// val FM_RECEIVER_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(PirateRadio.MOD_ID, "fm-receiver")) +// val FM_RECEIVER = register(Item(Item.Settings()), FM_RECEIVER_KEY) fun register(item: Item, registryKey: RegistryKey): Item { return Registry.register(Registries.ITEM, registryKey.value, item) diff --git a/src/main/kotlin/space/autistic/radio/PirateRadioRegistries.kt b/src/main/kotlin/space/autistic/radio/PirateRadioRegistries.kt new file mode 100644 index 0000000..6010d3c --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/PirateRadioRegistries.kt @@ -0,0 +1,12 @@ +package space.autistic.radio + +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder +import net.fabricmc.fabric.api.event.registry.RegistryAttribute + +object PirateRadioRegistries { + val ANTENNA_SERIALIZER = FabricRegistryBuilder.createSimple(PirateRadioRegistryKeys.ANTENNA_SERIALIZER) + .attribute(RegistryAttribute.SYNCED).buildAndRegister() + + fun initialize() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/PirateRadioRegistryKeys.kt b/src/main/kotlin/space/autistic/radio/PirateRadioRegistryKeys.kt new file mode 100644 index 0000000..eb5db1f --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/PirateRadioRegistryKeys.kt @@ -0,0 +1,13 @@ +package space.autistic.radio + +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.util.Identifier +import space.autistic.radio.PirateRadio.MOD_ID +import space.autistic.radio.antenna.Antenna +import space.autistic.radio.antenna.AntennaSerializer + +object PirateRadioRegistryKeys { + val ANTENNA_SERIALIZER = RegistryKey.ofRegistry>(Identifier.of(MOD_ID, "antenna_serializer")) + val ANTENNA = RegistryKey.ofRegistry>(Identifier.of(MOD_ID, "antenna")) +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/SidedProxy.kt b/src/main/kotlin/space/autistic/radio/SidedProxy.kt new file mode 100644 index 0000000..0e34ec9 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/SidedProxy.kt @@ -0,0 +1,9 @@ +package space.autistic.radio + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.ItemStack +import net.minecraft.util.Hand + +interface SidedProxy { + fun useStorageCard(player: PlayerEntity, item: ItemStack, hand: Hand) +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/antenna/Antenna.kt b/src/main/kotlin/space/autistic/radio/antenna/Antenna.kt new file mode 100644 index 0000000..c403081 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/antenna/Antenna.kt @@ -0,0 +1,9 @@ +package space.autistic.radio.antenna + +import space.autistic.radio.PirateRadioRegistries + +data class Antenna(val type: AntennaSerializer, val data: T) { + companion object { + val CODEC = PirateRadioRegistries.ANTENNA_SERIALIZER.codec.dispatch({ it.type }, AntennaSerializer<*>::codec) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/antenna/AntennaSerializer.kt b/src/main/kotlin/space/autistic/radio/antenna/AntennaSerializer.kt new file mode 100644 index 0000000..11d0234 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/antenna/AntennaSerializer.kt @@ -0,0 +1,8 @@ +package space.autistic.radio.antenna + +import com.mojang.serialization.MapCodec + +interface AntennaSerializer { + val codec: MapCodec> + get +} diff --git a/src/main/kotlin/space/autistic/radio/antenna/ConstAntenna.kt b/src/main/kotlin/space/autistic/radio/antenna/ConstAntenna.kt new file mode 100644 index 0000000..401972c --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/antenna/ConstAntenna.kt @@ -0,0 +1,7 @@ +package space.autistic.radio.antenna + +import com.mojang.serialization.Codec + +object ConstAntenna : AntennaSerializer { + override val codec = Codec.FLOAT.fieldOf("level").xmap({ Antenna(this, it) }, { it.data }) +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/antenna/PirateRadioAntennaSerializers.kt b/src/main/kotlin/space/autistic/radio/antenna/PirateRadioAntennaSerializers.kt new file mode 100644 index 0000000..19cfff8 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/antenna/PirateRadioAntennaSerializers.kt @@ -0,0 +1,18 @@ +package space.autistic.radio.antenna + +import net.minecraft.registry.Registry +import net.minecraft.util.Identifier +import space.autistic.radio.PirateRadio +import space.autistic.radio.PirateRadioRegistries + +object PirateRadioAntennaSerializers { + val CONST = register(Identifier.of(PirateRadio.MOD_ID, "const"), ConstAntenna) + val WASM = register(Identifier.of(PirateRadio.MOD_ID, "wasm"), WasmAntenna) + + private fun register(id: Identifier, antennaSerializer: AntennaSerializer): AntennaSerializer { + return Registry.register(PirateRadioRegistries.ANTENNA_SERIALIZER, id, antennaSerializer) + } + + fun initialize() { + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/antenna/WasmAntenna.kt b/src/main/kotlin/space/autistic/radio/antenna/WasmAntenna.kt new file mode 100644 index 0000000..32c96bb --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/antenna/WasmAntenna.kt @@ -0,0 +1,7 @@ +package space.autistic.radio.antenna + +import net.minecraft.util.Identifier + +object WasmAntenna : AntennaSerializer { + override val codec = Identifier.CODEC.fieldOf("model").xmap({ Antenna(this, it) }, { it.data }) +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/entity/DisposableTransmitterEntity.kt b/src/main/kotlin/space/autistic/radio/entity/DisposableTransmitterEntity.kt new file mode 100644 index 0000000..fe26a86 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/entity/DisposableTransmitterEntity.kt @@ -0,0 +1,178 @@ +package space.autistic.radio.entity + +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityType +import net.minecraft.entity.data.DataTracker +import net.minecraft.entity.data.TrackedDataHandlerRegistry +import net.minecraft.entity.decoration.AbstractDecorationEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.nbt.NbtCompound +import net.minecraft.network.listener.ClientPlayPacketListener +import net.minecraft.network.packet.Packet +import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket +import net.minecraft.server.network.EntityTrackerEntry +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand +import net.minecraft.util.math.* +import net.minecraft.world.World +import net.minecraft.world.event.GameEvent +import space.autistic.radio.PirateRadioComponents +import space.autistic.radio.PirateRadioEntityTypes +import space.autistic.radio.PirateRadioItems +import kotlin.math.max +import kotlin.math.min + +class DisposableTransmitterEntity : AbstractDecorationEntity { + var despawnDelay = 60 * 60 * 20 + + constructor(type: EntityType?, world: World?) : super(type, world) + + constructor( + type: EntityType?, + world: World?, + pos: BlockPos, + facing: Direction + ) : super( + type, world, pos + ) { + this.setFacing(facing) + } + + constructor( + world: World, + blockPos2: BlockPos, + direction: Direction + ) : this(PirateRadioEntityTypes.DISPOSABLE_TRANSMITTER, world, blockPos2, direction) + + override fun initDataTracker(builder: DataTracker.Builder) { + builder.add(TEXT, "") + builder.add(FREQUENCY, 768) + } + + override fun tick() { + super.tick() + if (!world.isClient) { + this.tickDespawnDelay(); + } + } + + private fun tickDespawnDelay() { + if (this.despawnDelay > 0 && this.despawnDelay-- == 0) { + this.discard() + } + } + + override fun setFacing(facing: Direction) { + this.facing = facing + if (facing.axis.isHorizontal) { + this.pitch = 0.0f + this.yaw = (this.facing.horizontal * 90).toFloat() + } else { + this.pitch = (-90 * facing.direction.offset()).toFloat() + this.yaw = 0.0f + } + + this.prevPitch = this.pitch + this.prevYaw = this.yaw + this.updateAttachmentPosition() + } + + override fun calculateBoundingBox(pos: BlockPos, side: Direction): Box { + val center = Vec3d.ofCenter(pos).offset(side, -(1.0 - DEPTH) / 2.0) + val axis = side.axis + val dx = if (axis === Direction.Axis.X) DEPTH else WIDTH + val dy = if (axis === Direction.Axis.Y) DEPTH else HEIGHT + val dz = if (axis === Direction.Axis.Z) DEPTH else WIDTH + return Box.of(center, dx, dy, dz) + } + + // leave this true for performance + override fun canStayAttached(): Boolean = true + + override fun onBreak(breaker: Entity?) { + // hmm, what to do here... + } + + override fun onPlace() { + // hmm, what to do here... + } + + var text: String + get() { + return this.dataTracker[TEXT] + } + set(value) { + this.dataTracker[TEXT] = value + } + + var frequency: Int + get() { + return this.dataTracker[FREQUENCY] + } + set(value) { + this.dataTracker[FREQUENCY] = value + } + + override fun writeCustomDataToNbt(nbt: NbtCompound) { + super.writeCustomDataToNbt(nbt) + nbt.putByte("Facing", this.facing.id.toByte()) + nbt.putInt("DespawnDelay", this.despawnDelay) + nbt.putString("Text", this.text) + nbt.putBoolean("Invisible", this.isInvisible) + nbt.putInt("Frequency", this.frequency) + } + + override fun readCustomDataFromNbt(nbt: NbtCompound) { + super.readCustomDataFromNbt(nbt) + this.despawnDelay = nbt.getInt("DespawnDelay") + this.setFacing(Direction.byId(nbt.getByte("Facing").toInt())) + this.text = nbt.getString("Text") + this.isInvisible = nbt.getBoolean("Invisible") + this.frequency = min(1080, max(768, nbt.getInt("Frequency"))) + } + + override fun createSpawnPacket(entityTrackerEntry: EntityTrackerEntry): Packet { + return EntitySpawnS2CPacket(this, facing.id, this.getAttachedBlockPos()) + } + + override fun onSpawnPacket(packet: EntitySpawnS2CPacket) { + super.onSpawnPacket(packet) + this.setFacing(Direction.byId(packet.entityData)) + } + + override fun getBodyYaw(): Float { + val direction = this.horizontalFacing + val i = if (direction.axis.isVertical) 90 * direction.direction.offset() else 0 + return MathHelper.wrapDegrees(180 + direction.horizontal * 90 + i).toFloat() + } + + override fun interact(player: PlayerEntity, hand: Hand): ActionResult { + val itemStack = player.getStackInHand(hand) + val noTextInTransmitter = this.text.isEmpty() + val isCard = itemStack.isOf(PirateRadioItems.STORAGE_CARD) + val holdingMessage = isCard && (itemStack.get(PirateRadioComponents.MESSAGE)?.literalString ?: "").isNotEmpty() + if (!world.isClient) { + if (noTextInTransmitter) { + if (holdingMessage && !this.isRemoved) { + this.frequency = itemStack.getOrDefault(PirateRadioComponents.FREQUENCY, 768) + this.text = itemStack.get(PirateRadioComponents.MESSAGE)?.literalString ?: "" + this.emitGameEvent(GameEvent.BLOCK_CHANGE, player) + itemStack.decrementUnlessCreative(1, player) + } + } + return ActionResult.CONSUME + } else { + return if (noTextInTransmitter && holdingMessage) ActionResult.SUCCESS else ActionResult.PASS + } + } + + companion object { + private val TEXT = + DataTracker.registerData(DisposableTransmitterEntity::class.java, TrackedDataHandlerRegistry.STRING) + private val FREQUENCY = + DataTracker.registerData(DisposableTransmitterEntity::class.java, TrackedDataHandlerRegistry.INTEGER) + const val DEPTH = 0.0625 + private const val WIDTH = 0.75 + private const val HEIGHT = 0.75 + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/entity/ElectronicsTraderEntity.kt b/src/main/kotlin/space/autistic/radio/entity/ElectronicsTraderEntity.kt index 3aa53b1..1acbeb4 100644 --- a/src/main/kotlin/space/autistic/radio/entity/ElectronicsTraderEntity.kt +++ b/src/main/kotlin/space/autistic/radio/entity/ElectronicsTraderEntity.kt @@ -21,7 +21,7 @@ class ElectronicsTraderEntity(entityType: EntityType?, + settings: Settings? +) : + ItemFrameItem(entityType, settings) { + + override fun useOnBlock(context: ItemUsageContext): ActionResult { + val blockPos = context.blockPos + val direction = context.side + val blockPos2 = blockPos.offset(direction) + val playerEntity = context.player + val itemStack = context.stack + if (playerEntity != null && !this.canPlaceOn(playerEntity, direction, itemStack, blockPos2)) { + return ActionResult.FAIL + } else { + val world = context.world + val abstractDecorationEntity: AbstractDecorationEntity + if (this.entityType === PirateRadioEntityTypes.DISPOSABLE_TRANSMITTER) { + abstractDecorationEntity = DisposableTransmitterEntity(world, blockPos2, direction) + } else { + return ActionResult.success(world.isClient) + } + + val nbtComponent = itemStack.getOrDefault(DataComponentTypes.ENTITY_DATA, NbtComponent.DEFAULT) + if (!nbtComponent.isEmpty) { + EntityType.loadFromEntityNbt(world, playerEntity, abstractDecorationEntity, nbtComponent) + } + + if (abstractDecorationEntity.canStayAttached()) { + if (!world.isClient) { + abstractDecorationEntity.onPlace() + world.emitGameEvent(playerEntity, GameEvent.ENTITY_PLACE, abstractDecorationEntity.pos) + world.spawnEntity(abstractDecorationEntity) + } + + itemStack.decrement(1) + return ActionResult.success(world.isClient) + } else { + return ActionResult.CONSUME + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/item/StorageCardItem.kt b/src/main/kotlin/space/autistic/radio/item/StorageCardItem.kt new file mode 100644 index 0000000..da1b057 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/item/StorageCardItem.kt @@ -0,0 +1,20 @@ +package space.autistic.radio.item + +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.stat.Stats +import net.minecraft.util.Hand +import net.minecraft.util.TypedActionResult +import net.minecraft.world.World +import space.autistic.radio.PirateRadio + +class StorageCardItem(settings: Settings) : Item(settings) { + + override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult { + val itemStack = user.getStackInHand(hand) + PirateRadio.proxy!!.useStorageCard(user, itemStack, hand) + user.incrementStat(Stats.USED.getOrCreateStat(this)) + return TypedActionResult.success(itemStack, world.isClient()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/network/StorageCardUpdateC2SPayload.kt b/src/main/kotlin/space/autistic/radio/network/StorageCardUpdateC2SPayload.kt new file mode 100644 index 0000000..9ffa75a --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/network/StorageCardUpdateC2SPayload.kt @@ -0,0 +1,27 @@ +package space.autistic.radio.network + +import net.minecraft.network.RegistryByteBuf +import net.minecraft.network.codec.PacketCodec +import net.minecraft.network.codec.PacketCodecs +import net.minecraft.network.packet.CustomPayload +import net.minecraft.util.Identifier +import space.autistic.radio.PirateRadio + +@JvmRecord +data class StorageCardUpdateC2SPayload(val slot: Int, val text: String, val frequency: Int) : CustomPayload { + override fun getId(): CustomPayload.Id = PAYLOAD_ID + + companion object { + val PAYLOAD_IDENTIFIER = Identifier.of(PirateRadio.MOD_ID, "storage-card-update") + val PAYLOAD_ID = CustomPayload.Id(PAYLOAD_IDENTIFIER) + val CODEC: PacketCodec = PacketCodec.tuple( + PacketCodecs.VAR_INT, + StorageCardUpdateC2SPayload::slot, + PacketCodecs.STRING, + StorageCardUpdateC2SPayload::text, + PacketCodecs.VAR_INT, + StorageCardUpdateC2SPayload::frequency, + ::StorageCardUpdateC2SPayload + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt b/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt new file mode 100644 index 0000000..cc123a1 --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt @@ -0,0 +1,173 @@ +package space.autistic.radio.wasm + +import com.dylibso.chicory.runtime.HostFunction +import com.dylibso.chicory.runtime.ImportFunction +import com.dylibso.chicory.runtime.Instance +import com.dylibso.chicory.wasm.types.Value +import com.dylibso.chicory.wasm.types.ValueType +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType + +class Bindings { + + companion object { + @JvmStatic + fun longToInt(long: Long): Int = long.toInt() + + @JvmStatic + fun stringArg(instance: Instance, address: Long): String { + return instance.memory().readCString(address.toInt()) + } + + @JvmStatic + fun boolArg(bool: Long): Boolean { + return bool != 0L + } + + @JvmStatic + fun intListArg(instance: Instance, argc: Long, argv: Long): List { + return IntArray(argc.toInt()) { + instance.memory().readInt(argv.toInt() + 4 * it) + }.toList() + } + + private val lookup = MethodHandles.lookup() + fun bindFunc( + module: String, + name: String, + inLookup: MethodHandles.Lookup, + method: Method, + receiver: Any + ): ImportFunction { + val baseHandle = inLookup.unreflect(method).bindTo(receiver) + val wasmParameters = ArrayList() + val filters = method.genericParameterTypes.map { + when (it) { + Int::class.java -> { + wasmParameters.add(ValueType.I32) + lookup.findStatic( + Bindings::class.java, "longToInt", MethodType.methodType(Int::class.java, Long::class.java) + ) + } + + Long::class.java -> { + wasmParameters.add(ValueType.I64) + MethodHandles.identity(Long::class.java) + } + + Float::class.java -> { + wasmParameters.add(ValueType.F32) + lookup.findStatic( + Value::class.java, "longToFloat", MethodType.methodType(Float::class.java, Long::class.java) + ) + } + + Double::class.java -> { + wasmParameters.add(ValueType.F64) + lookup.findStatic( + Value::class.java, + "longToDouble", + MethodType.methodType(Double::class.java, Long::class.java) + ) + } + + String::class.java -> { + wasmParameters.add(ValueType.I32) + lookup.findStatic( + Bindings::class.java, + "stringArg", + MethodType.methodType(String::class.java, Instance::class.java, Long::class.java) + ) + } + + Boolean::class.java -> { + wasmParameters.add(ValueType.I32) + lookup.findStatic( + Bindings::class.java, + "boolArg", + MethodType.methodType(Boolean::class.java, Long::class.java) + ) + } + + is ParameterizedType -> { + if (it.rawType == List::class.java) { + val converter = when (it.actualTypeArguments[0]) { + java.lang.Integer::class.java -> "intListArg" + else -> throw IllegalArgumentException(it.actualTypeArguments[0].toString()) + } + wasmParameters.add(ValueType.I32) + wasmParameters.add(ValueType.I32) + lookup.findStatic( + Bindings::class.java, + converter, + MethodType.methodType( + List::class.java, + Instance::class.java, + Long::class.java, + Long::class.java + ) + ) + } else { + throw IllegalArgumentException(it.toString()) + } + } + + else -> throw IllegalArgumentException(it.toString()) + } + } + val filterTypes = ArrayList>() + filters.forEach { methodHandle -> + filterTypes.addAll(methodHandle.type().parameterList()) + } + var i = 0 + val permutation = IntArray(filterTypes.size) { + if (filterTypes[it] == Instance::class.java) 0 else ++i + } + var handle = baseHandle + var j = 0 + filters.forEach { + handle = MethodHandles.collectArguments(handle, j, it) + j += it.type().parameterCount() + } + val newtype = MethodType.methodType( + baseHandle.type().returnType(), Instance::class.java, *Array(i) { Long::class.java }) + handle = MethodHandles.permuteArguments(handle, newtype, *permutation) + handle = handle.asSpreader(LongArray::class.java, i) + return when (method.genericReturnType) { + Void.TYPE -> HostFunction(module, name, wasmParameters, emptyList()) { instance, args -> + handle.invokeExact(instance, args) + Value.EMPTY_VALUES + } + + Int::class.java -> HostFunction(module, name, wasmParameters, listOf(ValueType.I32)) { instance, args -> + val result: Int = handle.invokeExact(instance, args) as Int + longArrayOf(result.toLong()) + } + + Boolean::class.java -> HostFunction( + module, + name, + wasmParameters, + listOf(ValueType.I32) + ) { instance, args -> + val result: Boolean = handle.invokeExact(instance, args) as Boolean + longArrayOf(if (result) 1L else 0L) + } + + Float::class.java -> HostFunction( + module, + name, + wasmParameters, + listOf(ValueType.F32) + ) { instance, args -> + val result: Float = handle.invokeExact(instance, args) as Float + longArrayOf(Value.floatToLong(result)) + } + + else -> throw IllegalArgumentException(method.genericReturnType.toString()) + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/pirate-radio/blockstates/disposable-transmitter.json b/src/main/resources/assets/pirate-radio/blockstates/disposable-transmitter.json new file mode 100644 index 0000000..854abc8 --- /dev/null +++ b/src/main/resources/assets/pirate-radio/blockstates/disposable-transmitter.json @@ -0,0 +1,10 @@ +{ + "variants": { + "facing=down": { "model": "pirate-radio:block/disposable-transmitter-vertical" }, + "facing=up": { "model": "pirate-radio:block/disposable-transmitter-vertical" }, + "facing=north": { "model": "pirate-radio:block/disposable-transmitter" }, + "facing=south": { "model": "pirate-radio:block/disposable-transmitter" }, + "facing=west": { "model": "pirate-radio:block/disposable-transmitter" }, + "facing=east": { "model": "pirate-radio:block/disposable-transmitter" } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/pirate-radio/lang/en_us.json b/src/main/resources/assets/pirate-radio/lang/en_us.json index 9627729..81145f0 100644 --- a/src/main/resources/assets/pirate-radio/lang/en_us.json +++ b/src/main/resources/assets/pirate-radio/lang/en_us.json @@ -6,5 +6,25 @@ "item.pirate-radio.disposable-transmitter": "Disposable Pirate Radio Transmitter", "item.pirate-radio.fm-receiver": "FM Receiver", "entity.pirate-radio.electronics-trader": "Microcenter", - "pirate-radio.fm-receiver": "FM Receiver" + "pirate-radio.fm-receiver": "FM Receiver", + "pirate-radio.skin-pack": "Skin pack: %s", + "pirate-radio.frequency.plus": "+", + "pirate-radio.frequency.minus": "-", + "pirate-radio.volume.plus": "+", + "pirate-radio.volume.minus": "-", + "pirate-radio.mode": "Mode", + "pirate-radio.frequency.plus.narrated": "Increase Frequency", + "pirate-radio.frequency.minus.narrated": "Decrease Frequency", + "pirate-radio.volume.plus.narrated": "Increase Volume", + "pirate-radio.volume.minus.narrated": "Decrease Volume", + "pirate-radio.mode.selected": "Mode: %s", + "pirate-radio.mode.full": "Full", + "pirate-radio.mode.fast": "Fast", + "pirate-radio.mode.deaf": "Deaf", + "pirate-radio.volume.selected": "Volume: %s", + "pirate-radio.volume.off": "Off", + "pirate-radio.frequency.selected": "Frequency: %s.%s MHz", + "pirate-radio.storage-card": "SD Card", + "pirate-radio.message": "Message...", + "pirate-radio.frequency.edit": "Frequency" } \ No newline at end of file diff --git a/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter-vertical.json b/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter-vertical.json new file mode 100644 index 0000000..39dc38e --- /dev/null +++ b/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter-vertical.json @@ -0,0 +1,29 @@ +{ + "textures": { + "antenna": "minecraft:block/iron_block", + "body": "minecraft:block/coal_block" + }, + "elements": [ + { "from": [ 6.5, 6.9, 15.4 ], + "to": [ 9.5, 9.1, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" } + } + }, + { "from": [ 7, 7, 9 ], + "to": [ 7.1, 7.1, 15.4 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" } + } + } + ] +} diff --git a/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter.json b/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter.json new file mode 100644 index 0000000..f5e26dc --- /dev/null +++ b/src/main/resources/assets/pirate-radio/models/block/disposable-transmitter.json @@ -0,0 +1,30 @@ +{ + "textures": { + "antenna": "minecraft:block/iron_block", + "body": "minecraft:block/coal_block" + }, + "elements": [ + { "from": [ 6.5, 0.9, 15.4 ], + "to": [ 9.5, 3.1, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#body" } + } + }, + { "from": [ 7, 1, 15.3 ], + "to": [ 7.1, 7.5, 15.4 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#antenna" } + } + } + ] +} diff --git a/src/main/resources/assets/pirate-radio/textures/entity/electronics-trader.png b/src/main/resources/assets/pirate-radio/textures/entity/electronics-trader.png new file mode 100644 index 0000000..b86a4ef Binary files /dev/null and b/src/main/resources/assets/pirate-radio/textures/entity/electronics-trader.png differ diff --git a/src/main/resources/assets/pirate-radio/textures/gui/radio-receiver.png b/src/main/resources/assets/pirate-radio/textures/gui/radio-receiver.png new file mode 100644 index 0000000..b86a4ef Binary files /dev/null and b/src/main/resources/assets/pirate-radio/textures/gui/radio-receiver.png differ diff --git a/src/main/resources/assets/pirate-radio/textures/item/disposable-transmitter.png b/src/main/resources/assets/pirate-radio/textures/item/disposable-transmitter.png new file mode 100644 index 0000000..c815369 Binary files /dev/null and b/src/main/resources/assets/pirate-radio/textures/item/disposable-transmitter.png differ diff --git a/src/main/resources/data/pirate-radio/pirate-radio/antenna/const.json b/src/main/resources/data/pirate-radio/pirate-radio/antenna/const.json new file mode 100644 index 0000000..6f7a630 --- /dev/null +++ b/src/main/resources/data/pirate-radio/pirate-radio/antenna/const.json @@ -0,0 +1,4 @@ +{ + "type": "pirate-radio:const", + "level": 1.0 +} \ No newline at end of file diff --git a/src/main/resources/data/pirate-radio/pirate-radio/antenna/null.json b/src/main/resources/data/pirate-radio/pirate-radio/antenna/null.json new file mode 100644 index 0000000..6b7a819 --- /dev/null +++ b/src/main/resources/data/pirate-radio/pirate-radio/antenna/null.json @@ -0,0 +1,4 @@ +{ + "type": "pirate-radio:const", + "level": 0.0 +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 71f2518..c7d859a 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -34,6 +34,12 @@ } ] }, + "mixins": [ + { + "config": "pirate-radio.client-mixins.json", + "environment": "client" + } + ], "depends": { "fabricloader": ">=0.16.10", "minecraft": "~1.21.1", diff --git a/src/main/resources/pirate-radio.client-mixins.json b/src/main/resources/pirate-radio.client-mixins.json new file mode 100644 index 0000000..27a5861 --- /dev/null +++ b/src/main/resources/pirate-radio.client-mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "space.autistic.radio.client.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "client": [ + "BlockStatesLoaderMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/src/test/kotlin/space/autistic/radio/complex/ComplexKtTest.kt b/src/test/kotlin/space/autistic/radio/complex/ComplexKtTest.kt index a4dfe91..1a6446b 100644 --- a/src/test/kotlin/space/autistic/radio/complex/ComplexKtTest.kt +++ b/src/test/kotlin/space/autistic/radio/complex/ComplexKtTest.kt @@ -2,6 +2,8 @@ package space.autistic.radio.complex import org.joml.Vector2f import org.junit.jupiter.api.Assertions.* +import space.autistic.radio.client.complex.I +import space.autistic.radio.client.complex.cmul import kotlin.test.Test class ComplexKtTest { diff --git a/src/test/kotlin/space/autistic/radio/fmsim/TestAsserts.kt b/src/test/kotlin/space/autistic/radio/fmsim/TestAsserts.kt index 8a4862c..aaa6db3 100644 --- a/src/test/kotlin/space/autistic/radio/fmsim/TestAsserts.kt +++ b/src/test/kotlin/space/autistic/radio/fmsim/TestAsserts.kt @@ -1,13 +1,25 @@ package space.autistic.radio.fmsim +import space.autistic.radio.client.fmsim.FmFullConstants +import space.autistic.radio.client.fmsim.FmFullDemodulator +import space.autistic.radio.client.fmsim.FmFullModulator +import java.nio.FloatBuffer import kotlin.test.Test class TestAsserts { @Test - fun testFmFullSim() { - // initialize and flush an FM modulator - // if anything asserts, this should catch it + fun testFmFullMod() { + // initialize and flush an FM modulator, in both mono and stereo val fmFullModulator = FmFullModulator() - fmFullModulator.flush(1f) {} + fmFullModulator.flush(1f, FmFullConstants.STEREO) {} + fmFullModulator.flush(1f, FmFullConstants.MONO) {} + } + + @Test + fun testFmFullDemod() { + // initialize and flush an FM demodulator, in both mono and stereo + val fmFullDemodulator = FmFullDemodulator() + fmFullDemodulator.flush(FmFullConstants.STEREO) { _, _ -> } + fmFullDemodulator.flush(FmFullConstants.MONO) { _, _ -> } } } \ No newline at end of file -- cgit 1.4.1