joy/build.gradle.kts
2025-03-13 15:14:18 -07:00

263 lines
8.3 KiB
Plaintext

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import java.io.IOException
import java.nio.file.Files
import javax.imageio.ImageIO
plugins {
java
id("gay.ampflower.BuildPlugin")
alias(libs.plugins.loom)
alias(libs.plugins.minotaur)
}
val excluded = setOf(rootProject, project(":xplat"))
allprojects {
apply(plugin = "java")
apply(plugin = "gay.ampflower.BuildPlugin")
apply(plugin = rootProject.libs.plugins.loom.get().pluginId)
apply(plugin = rootProject.libs.plugins.minotaur.get().pluginId)
val libs = rootProject.libs
version = meta.globalVersion
base {
if (project != rootProject) {
archivesName.set(rootProject.name + '-' + project.name)
}
}
loom {
mixin {
defaultRefmapName = "joy.refmap.json"
}
splitEnvironmentSourceSets()
runs {
named("client") {
client()
configName = "${project.name.replaceFirstChar(Char::uppercase)} Client"
ideConfigGenerated(project !in excluded)
runDir(rootProject.relativePath("run"))
}
named("server") {
server()
configName = "${project.name.replaceFirstChar(Char::uppercase)} Server"
ideConfigGenerated(project !in excluded)
runDir(rootProject.relativePath("run"))
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
withSourcesJar()
}
repositories {
mavenCentral()
maven("https://maven.neoforged.net/releases") { name = "Neoforged" }
maven("https://files.minecraftforge.net/maven/") { name = "Forge" }
maven("https://maven.quiltmc.org/repository/release") { name = "Quilt" }
maven("https://repo.sleeping.town") {
name = "Sleeping Town"
content {
includeGroup("com.unascribed")
}
}
maven("https://api.modrinth.com/maven") { name = "Modrinth" }
maven("https://maven.terraformersmc.com") { name = "TerraformersMC" }
maven("https://maven.ladysnake.org/releases") { name = "Ladysnake Libs" }
maven("https://maven.theillusivec4.top/") { name = "TheIllusiveC4" }
maven("https://maven.bawnorton.com/releases") { name = "Bawnorton" }
}
dependencies {
minecraft(libs.minecraft)
mappings(variantOf(libs.yarn) { classifier("v2") })
modCompileOnly(libs.bundles.common.compile)
annotationProcessor(libs.mixin.squared) {
exclude(module = "fabric-loader")
}
modImplementation(libs.bundles.common.bundle)
if (project !in excluded) {
include(libs.bundles.common.bundle)
}
annotationProcessor(libs.mixin.extras)
modCompileOnly(libs.mixin.extras)
}
tasks {
withType<ProcessResources> {
if (project !in excluded) {
project(":xplat").afterEvaluate { dependsOn(tasks.named("runDatagen")) }
}
val map = mapOf(
"id" to mod.id.get(),
"version" to version,
"java" to java.targetCompatibility.majorVersion,
"loader" to libs.versions.fabric.loader.get(),
"description" to project.description,
"minecraftRequired" to libs.versions.minecraft.required.get(),
)
inputs.properties(map)
filesMatching(listOf("fabric.mod.json", "quilt.mod.json", "META-INF/mods.toml")) {
expand(map)
}
exclude("*/.editorconfig", ".cache/")
}
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release = 21
}
withType<Jar> {
if (project !in excluded) {
project(":xplat").afterEvaluate { dependsOn(tasks.named("runDatagen")) }
}
rootDir.listFiles { _, name -> name.startsWith("LICENSE") }?.let {
from(it) {
rename { file -> "${rootProject.name}-${file}" }
}
}
}
register("publish")
}
}
tasks {
register("imageCleanup") {
val prop = System.getProperties()
val cleanupSource = prop.getProperty("cleanupSource")
notCompatibleWithConfigurationCache("not ready for caching")
onlyIf { cleanupSource != null }
if (cleanupSource != null) {
inputs.files(cleanupSource, sourceSets.main.get().resources)
}
doLast {
data class ImgData(
val file: File,
val image: IntArray,
val badMips: Boolean,
val update: MutableSet<File> = HashSet(),
val hit: MutableSet<File> = HashSet()
) {
override fun toString(): String {
return "$file => ${image.size} pixels\n\tupdate => $update\n\thit => $hit"
}
fun isMissed(): Boolean = update.isEmpty() && hit.isEmpty()
}
val map = Long2ObjectOpenHashMap<ImgData>()
fun doWork(file: File) {
if (file.extension != "png") {
return
}
val img = ImageIO.read(file)
val w = img.width
val h = img.height
val data = img.getRGB(0, 0, w, h, null, 0, w)
var hash = 0L
for (i in data.indices) {
val c = data[i]
if (c and 0xFF000000.toInt() == 0) {
data[i] = 0
}
}
for (d in data) {
hash = hash * 31L + d
}
val other = map.get(hash)
// Kotlin kept randomly stating that the variable other is always null,
// then had the audacity to tell me the suppression was redundant.
@Suppress("KotlinConstantConditions", "RedundantSuppression")
if (other == null) {
map.put(hash, ImgData(file, data, (w and 15) or (w and 15) != 0))
return
}
if (!other.image.contentEquals(data)) {
logger.warn("{} != {}", file, other.file)
return
}
val selfSize = Files.size(file.toPath())
val otherSize = Files.size(other.file.toPath())
if (selfSize < otherSize) {
logger.info("{} < {} => {} < {}; replace", file, other.file, selfSize, otherSize)
map.put(hash, ImgData(file, data, other.badMips, update = HashSet(other.update + other.hit + other.file)))
return
}
if (selfSize > otherSize) {
logger.info("{} > {} => {} > {}; update", file, other.file, selfSize, otherSize)
other.update += file
return
}
logger.info("{} = {} => {} = {}; hit", file, other.file, selfSize, otherSize)
other.hit += file
}
for (file in project.fileTree(cleanupSource)) {
doWork(file)
}
for (file in sourceSets.main.get().resources) {
doWork(file)
}
for ((k, v) in map) {
logger.info("{} => {}", k, v)
}
map.values.asSequence()
.filterNot { it.isMissed() }
.map { it.file to it.update }
.forEach {
val src = it.first.readBytes()
it.second.forEach { dest ->
try {
dest.writeBytes(src)
} catch (e: IOException) {
logger.error("Failed to copy {} => {}", src, dest, e)
}
}
}
map.values.asSequence()
.filter { it.isMissed() }
.forEach { logger.warn("Missed entry: {}", it.file) }
map.values.asSequence()
.filter { it.badMips }
.forEach { logger.warn("Bad mipmaps: {}", it) }
}
}
}