diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4fdf414 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,90 @@ +name: Build + +on: + push: + tags: + - "v*" + branches: + - "**" + pull_request: + workflow_dispatch: + inputs: + loader: + description: Mod loader to build and publish + required: true + default: both + type: choice + options: + - both + - fabric + - neoforge + release_type: + description: CurseForge release channel + required: true + default: alpha + type: choice + options: + - alpha + - beta + - release + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + + - name: Build Fabric + if: github.event_name != 'workflow_dispatch' || inputs.loader == 'both' || inputs.loader == 'fabric' + run: ./gradlew :fabric:build + + - name: Build NeoForge + if: github.event_name != 'workflow_dispatch' || inputs.loader == 'both' || inputs.loader == 'neoforge' + run: ./gradlew :neoforge:build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: openfm-${{ github.ref_name }} + path: | + fabric/build/libs/*.jar + neoforge/build/libs/*.jar + !fabric/build/libs/*-sources.jar + !neoforge/build/libs/*-sources.jar + if-no-files-found: error + + - name: Publish Fabric to CurseForge + if: (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && (github.event_name != 'workflow_dispatch' || inputs.loader == 'both' || inputs.loader == 'fabric') + env: + ORG_GRADLE_PROJECT_curseforge_project: ${{ vars.CURSEFORGE_PROJECT_ID }} + ORG_GRADLE_PROJECT_curseforge_token: ${{ secrets.CURSEFORGE_TOKEN }} + ORG_GRADLE_PROJECT_curseforge_release_type: ${{ github.event_name == 'workflow_dispatch' && inputs.release_type || 'release' }} + ORG_GRADLE_PROJECT_curseforge_changelog: OpenFM ${{ github.ref_name }} from ${{ github.sha }} + run: ./gradlew :fabric:publishCurseForge --no-configuration-cache + + - name: Publish NeoForge to CurseForge + if: (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && (github.event_name != 'workflow_dispatch' || inputs.loader == 'both' || inputs.loader == 'neoforge') + env: + ORG_GRADLE_PROJECT_curseforge_project: ${{ vars.CURSEFORGE_PROJECT_ID }} + ORG_GRADLE_PROJECT_curseforge_token: ${{ secrets.CURSEFORGE_TOKEN }} + ORG_GRADLE_PROJECT_curseforge_release_type: ${{ github.event_name == 'workflow_dispatch' && inputs.release_type || 'release' }} + ORG_GRADLE_PROJECT_curseforge_changelog: OpenFM ${{ github.ref_name }} from ${{ github.sha }} + run: ./gradlew :neoforge:publishCurseForge --no-configuration-cache diff --git a/.gitignore b/.gitignore index 12f8644..9746858 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ build # other eclipse run +runs # Files from Forge MDK forge*changelog.txt diff --git a/README.md b/README.md index 09c8940..c02b03f 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,43 @@ # OpenFM -Streaming radio for Minecraft, with OpenComputers, and ComputerCraft support. +OpenFM adds streaming internet radio blocks and linked speakers to Minecraft 1.21.1, with optional CC:Tweaked peripheral support. -fm = component.openfm_radio +## Loaders -fm.setURL("http://listen.radionomy.com/DeepHouse") +OpenFM is maintained as a multi-loader project: -fm.start() +- `common` contains shared gameplay, networking payloads, UI, rendering, and streaming audio code. +- `neoforge` contains the NeoForge entrypoint and loader integrations. +- `fabric` contains the Fabric entrypoints and loader integrations. -fm.stop() +The loader modules compile the same common sources. Loader-specific behavior is kept behind the registration and platform service interfaces under `me.michiyo.openfm.platform`. -fm.volUp() +## Building -fm.volDown() +Java 21 is required. -fm.getVol() +```powershell +.\gradlew.bat :neoforge:build :fabric:build +``` -fm.setScreenColor(0xFF0000) Sets the text color to red on the display +The distributable JARs are written to: -fm.setScreenText("Text to display on radio") Text will scroll if larger than the display. +- `neoforge/build/libs/openfm-neoforge-1.21.1-.jar` +- `fabric/build/libs/openfm-fabric-1.21.1-.jar` +Both JARs embed the MP3, Ogg Vorbis, and AAC decoder libraries. The Fabric build requires Fabric API. CC:Tweaked is optional on both loaders. -Highly modified version of Dragon's Radio Mod, Dragon's Radio Mod is Licenses MIT. +## Development runs -Downloads via Curse http://www.curse.com/mc-mods/Minecraft/236839-openfm +```powershell +.\gradlew.bat :neoforge:runClient +.\gradlew.bat :neoforge:runServer +.\gradlew.bat :fabric:runClient +.\gradlew.bat :fabric:runServer +``` + +## CC:Tweaked + +When CC:Tweaked is installed, the radio is exposed as an OpenFM peripheral. Existing Lua-facing methods and events are shared between both loader builds. + +OpenFM is a heavily modified version of Dragon's Radio Mod. Both projects are licensed under the MIT License. diff --git a/build.gradle b/build.gradle index ec6d80a..a917db6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,69 +1,11 @@ plugins { - id 'eclipse' - id 'maven-publish' - id 'net.minecraftforge.gradle' version '6.0.+' + id 'fabric-loom' version '1.8-SNAPSHOT' apply false + id 'net.neoforged.moddev' version '2.0.49-beta' apply false + id 'net.darkhax.curseforgegradle' version '1.1.28' apply false } -group = project.mod_group -version = project.mod_version -archivesBaseName = project.mod_id - -java.toolchain.languageVersion = JavaLanguageVersion.of(17) - -minecraft { - mappings channel: 'official', version: project.minecraft_version - - runs { - client { - workingDirectory project.file('run') - property 'forge.logging.console.level', 'debug' - mods { - "${project.mod_id}" { - source sourceSets.main - } - } - } - - server { - workingDirectory project.file('run') - property 'forge.logging.console.level', 'debug' - mods { - "${project.mod_id}" { - source sourceSets.main - } - } - } - } -} - -repositories { - mavenCentral() - - // CC:Tweaked Maven (note: maven.squiddev.cc, NOT squiddev.cc/maven) - maven { - url "https://maven.squiddev.cc" - content { includeGroup "cc.tweaked" } - } -} - -dependencies { - minecraft "net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}" - - // Compile-time - implementation "javazoom:jlayer:1.0.1" - implementation "org.jcraft:jorbis:0.0.17" - - // ComputerCraft (CC:Tweaked) - ForgeGradle deobf deps - // API at compile-time, full mod at runtime for dev runs. - compileOnly "cc.tweaked:cc-tweaked-1.20.1-core-api:1.117.0" - compileOnly fg.deobf("cc.tweaked:cc-tweaked-1.20.1-forge-api:1.117.0") - runtimeOnly fg.deobf("cc.tweaked:cc-tweaked-1.20.1-forge:1.117.0") - - // DEV runtime: put libs on the game/launch classpath so the mod classloader can see them - minecraftLibrary "javazoom:jlayer:1.0.1" - minecraftLibrary "org.jcraft:jorbis:0.0.17" -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' +tasks.register('publishCurseForge') { + group = 'publishing' + description = 'Build and publish both the Fabric and NeoForge JARs to CurseForge.' + dependsOn(':fabric:publishCurseForge', ':neoforge:publishCurseForge') } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..afdd0b1 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,4 @@ +plugins { + id 'groovy-gradle-plugin' +} + diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle new file mode 100644 index 0000000..2798bc1 --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -0,0 +1,77 @@ +plugins { + id 'java-library' + id 'maven-publish' +} + +base { + archivesName = "${mod_id}-${project.name}-${minecraft_version}" +} + +java { + toolchain.languageVersion = JavaLanguageVersion.of(java_version) + withSourcesJar() +} + +repositories { + mavenCentral() + maven { + name = 'CC-Tweaked' + url = 'https://maven.squiddev.cc' + content { includeGroup 'cc.tweaked' } + } + exclusiveContent { + forRepositories( + maven { + name = 'ParchmentMC' + url = 'https://maven.parchmentmc.org/' + }, + maven { + name = 'NeoForge' + url = 'https://maven.neoforged.net/releases' + } + ) + filter { includeGroup 'org.parchmentmc.data' } + } +} + +['apiElements', 'runtimeElements', 'sourcesElements'].each { variant -> + configurations."$variant".outgoing { + capability("$group:${base.archivesName.get()}:$version") + capability("$group:$mod_id-${project.name}-${minecraft_version}:$version") + capability("$group:$mod_id:$version") + } + publishing.publications.configureEach { + suppressPomMetadataWarningsFor(variant) + } +} + +tasks.withType(Jar).configureEach { + from(rootProject.file('LICENSE')) { + rename { "${it}_${mod_name}" } + } +} + +processResources { + def expandProps = [ + version: version, + minecraft_version: minecraft_version, + minecraft_version_range: minecraft_version_range, + fabric_version: fabric_version, + fabric_loader_version: fabric_loader_version, + mod_name: mod_name, + mod_author: mod_author, + mod_id: mod_id, + license: license, + description: project.description, + neoforge_version: neoforge_version, + neoforge_loader_version_range: neoforge_loader_version_range, + cc_tweaked_version: cc_tweaked_version, + cc_tweaked_min_version: cc_tweaked_min_version, + credits: credits, + java_version: java_version + ] + filesMatching(['pack.mcmeta', 'fabric.mod.json', 'META-INF/neoforge.mods.toml']) { + expand expandProps + } + inputs.properties(expandProps) +} diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle new file mode 100644 index 0000000..467b08c --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-loader.gradle @@ -0,0 +1,34 @@ +plugins { + id 'multiloader-common' +} + +configurations { + commonJava { canBeResolved = true } + commonResources { canBeResolved = true } +} + +dependencies { + compileOnly(project(':common')) { + capabilities { requireCapability "$group:$mod_id" } + } + commonJava project(path: ':common', configuration: 'commonJava') + commonResources project(path: ':common', configuration: 'commonResources') +} + +tasks.named('compileJava', JavaCompile) { + dependsOn(configurations.commonJava) + source(configurations.commonJava) +} + +processResources { + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} + +tasks.named('sourcesJar', Jar) { + dependsOn(configurations.commonJava) + from(configurations.commonJava) + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} + diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..a8af6aa --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'multiloader-common' + id 'net.neoforged.moddev' +} + +neoForge { + neoFormVersion = neo_form_version + parchment { + minecraftVersion = parchment_minecraft + mappingsVersion = parchment_version + } +} + +dependencies { + api 'javazoom:jlayer:1.0.1' + api 'org.jcraft:jorbis:0.0.17' + api 'com.tianscar.javasound:jaad:0.9.4' + compileOnly "cc.tweaked:cc-tweaked-${minecraft_version}-core-api:${cc_tweaked_neoforge_version}" +} + +configurations { + commonJava { + canBeResolved = false + canBeConsumed = true + } + commonResources { + canBeResolved = false + canBeConsumed = true + } +} + +artifacts { + commonJava sourceSets.main.java.sourceDirectories.singleFile + commonResources sourceSets.main.resources.sourceDirectories.singleFile +} diff --git a/src/main/java/me/michiyo/openfm.zip b/common/src/main/java/me/michiyo/openfm.zip similarity index 100% rename from src/main/java/me/michiyo/openfm.zip rename to common/src/main/java/me/michiyo/openfm.zip diff --git a/common/src/main/java/me/michiyo/openfm/OpenFM.java b/common/src/main/java/me/michiyo/openfm/OpenFM.java new file mode 100644 index 0000000..87c9697 --- /dev/null +++ b/common/src/main/java/me/michiyo/openfm/OpenFM.java @@ -0,0 +1,27 @@ +package me.michiyo.openfm; + +import me.michiyo.openfm.platform.OpenFMRegistrar; +import me.michiyo.openfm.reg.ModBlockEntities; +import me.michiyo.openfm.reg.ModBlocks; +import me.michiyo.openfm.reg.ModCreativeTabs; +import me.michiyo.openfm.reg.ModItems; +import me.michiyo.openfm.reg.ModMenus; + +public final class OpenFM { + public static final String MODID = "openfm"; + private static boolean initialized; + + public static synchronized void init(OpenFMRegistrar registrar) { + if (initialized) return; + + ModBlocks.register(registrar); + ModItems.register(registrar); + ModBlockEntities.register(registrar); + ModMenus.register(registrar); + ModCreativeTabs.register(registrar); + initialized = true; + } + + private OpenFM() {} +} + diff --git a/common/src/main/java/me/michiyo/openfm/client/ClientEvents.java b/common/src/main/java/me/michiyo/openfm/client/ClientEvents.java new file mode 100644 index 0000000..521dc7e --- /dev/null +++ b/common/src/main/java/me/michiyo/openfm/client/ClientEvents.java @@ -0,0 +1,12 @@ +package me.michiyo.openfm.client; + +import me.michiyo.openfm.client.audio.RadioStreamManager; + +public final class ClientEvents { + public static void clientTick() { + RadioStreamManager.clientTick(); + } + + private ClientEvents() {} +} + diff --git a/common/src/main/java/me/michiyo/openfm/client/OpenFMClient.java b/common/src/main/java/me/michiyo/openfm/client/OpenFMClient.java new file mode 100644 index 0000000..1b2d2be --- /dev/null +++ b/common/src/main/java/me/michiyo/openfm/client/OpenFMClient.java @@ -0,0 +1,27 @@ +package me.michiyo.openfm.client; + +import me.michiyo.openfm.client.screen.OpenFMAudioSettingsScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.options.OptionsScreen; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +public final class OpenFMClient { + public static void addSettingsButton(Screen screen, Consumer