Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ repositories {
}
maven { url = 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/' }
maven { url = "https://maven.blamejared.com/" } // JEI
maven {
name = "Sable"
url = uri("https://maven.ryanhcode.dev/releases")
content { includeGroup "dev.ryanhcode.sable-companion" }
}
}

base {
Expand Down Expand Up @@ -132,6 +137,8 @@ dependencies {

implementation("software.bernie.geckolib:geckolib-neoforge-1.21.1:4.7.1")

compileOnly("dev.ryanhcode.sable-companion:sable-companion-common-${minecraft_version}:${sable_companion_version}")

// WebP thumbnail support (pure Java ImageIO plugin)
// implementation = compile classpath, jarJar = bundled in prod, additionalRuntimeClasspath = dev runtime
implementation "com.twelvemonkeys.imageio:imageio-webp:3.12.0"
Expand Down Expand Up @@ -234,6 +241,7 @@ if (System.getenv("CURSEFORGE_TOKEN")) {
mainFile.addRequirement('geckolib')
mainFile.addOptional('curios')
mainFile.addOptional('jei')
mainFile.addOptional('sable')
}
}

Expand All @@ -253,6 +261,7 @@ if (System.getenv("MODRINTH_TOKEN")) {
required.project 'geckolib'
optional.project 'curios'
optional.project 'jei'
optional.project 'sable'
}
}
}
7 changes: 4 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@ minecraft_version=1.21.1
# as they do not follow standard versioning conventions.
minecraft_version_range=[1.21.1]
# The Neo version must agree with the Minecraft version to get a valid artifact
neo_version=21.1.206
neo_version=21.1.262
# The Neo version range can use any version of Neo as bounds
neo_version_range=[21,)
# The loader version range can only use the major version of FML as bounds
loader_version_range=[5.3,)
parchment_minecraft_version=1.21.1
parchment_mappings_version=2024.11.17
## Create
create_version=6.0.9-215
ponder_version=1.0.81
create_version=6.0.10-281
ponder_version=1.0.87
flywheel_version=1.0.6
registrate_version=MC1.21-1.3.0+67
#jei
jei_version=19.21.0.247
#curios
curios_minecraft_version=1.21.1
curios_version=9.2.2
sable_companion_version=1.6.0
## Mod Properties
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/de/devin/cbbees/CreateBuzzyBeez.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.simibubi.create.foundation.data.CreateRegistrate
import com.simibubi.create.foundation.item.ItemDescription
import com.simibubi.create.foundation.item.KineticStats
import com.simibubi.create.foundation.item.TooltipModifier
import de.devin.cbbees.compat.sable.SableCompat
import de.devin.cbbees.compat.sable.SubLevelNetworkTracker
import de.devin.cbbees.blocks.AllBlocks
import de.devin.cbbees.config.CBBeesClientConfig
import de.devin.cbbees.config.CBBeesConfig
Expand Down Expand Up @@ -182,6 +184,7 @@ object CreateBuzzyBeez {
// Register server-side event handlers on the NeoForge event bus
NeoForge.EVENT_BUS.register(CCRServerEvents::class.java)
NeoForge.EVENT_BUS.register(PlayerTickEvent())
if (SableCompat.isLoaded) NeoForge.EVENT_BUS.register(SubLevelNetworkTracker::class.java)
}

fun asResource(path: String): ResourceLocation {
Expand Down
36 changes: 36 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/CompanionSubLevels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.devin.cbbees.compat.sable

import dev.ryanhcode.sable.companion.SableCompanion
import dev.ryanhcode.sable.companion.math.Pose3dc
import net.minecraft.core.BlockPos
import net.minecraft.core.Position
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import org.joml.Quaterniond
import org.joml.Vector3d
import java.util.UUID

object CompanionSubLevels : SubLevelProvider {

private val companion: SableCompanion get() = SableCompanion.INSTANCE

override fun subLevelId(level: Level, pos: BlockPos): UUID? =
companion.getContaining(level, pos)?.uniqueId

override fun toWorld(level: Level, pos: Vec3): Vec3 =
companion.projectOutOfSubLevel(level, pos as Position)

override fun worldDistanceSqr(level: Level, a: Vec3, b: Vec3): Double =
companion.distanceSquaredWithSubLevels(level, a, b)

fun clientFrame(pos: BlockPos, partialTick: Float): SubLevelFrame? =
companion.getContainingClient(pos)?.let { frameOf(it.uniqueId, it.renderPose(partialTick)) }

private fun frameOf(id: UUID, pose: Pose3dc) = SubLevelFrame(
id,
Vector3d(pose.position()),
Quaterniond(pose.orientation()),
Vector3d(pose.rotationPoint()),
Vector3d(pose.scale())
)
}
23 changes: 23 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/FlightSpace.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.devin.cbbees.compat.sable

import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf

interface FlightSpace {
fun crossings(level: Level, positions: List<BlockPos>): Set<Int>
fun pose(from: BlockPos, to: BlockPos, t: Double, offset: Vec3, partialTick: Float): FlightPose
fun heading(from: BlockPos, to: BlockPos): Vec3
}

data class FlightPose(val position: Vec3, val rotation: Quaternionf?)

object FlatFlightSpace : FlightSpace {
override fun crossings(level: Level, positions: List<BlockPos>): Set<Int> = emptySet()

override fun pose(from: BlockPos, to: BlockPos, t: Double, offset: Vec3, partialTick: Float) =
FlightPose(Vec3.atCenterOf(from).lerp(Vec3.atCenterOf(to), t).add(offset), null)

override fun heading(from: BlockPos, to: BlockPos): Vec3 = Vec3.atCenterOf(to).subtract(Vec3.atCenterOf(from))
}
18 changes: 18 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/RenderSpace.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.devin.cbbees.compat.sable

import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.Vec3
import java.util.function.Consumer

interface RenderSpace {
fun draw(poseStack: PoseStack, anchor: BlockPos, camera: Vec3, partialTick: Float, render: Consumer<Vec3>)
fun toLocal(anchor: BlockPos, world: Vec3, partialTick: Float): Vec3
}

object FlatRenderSpace : RenderSpace {
override fun draw(poseStack: PoseStack, anchor: BlockPos, camera: Vec3, partialTick: Float, render: Consumer<Vec3>) =
render.accept(camera)

override fun toLocal(anchor: BlockPos, world: Vec3, partialTick: Float): Vec3 = world
}
34 changes: 34 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/SableCompat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package de.devin.cbbees.compat.sable

import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import java.util.UUID

object SableCompat {

private const val COMPANION_CLASS = "dev.ryanhcode.sable.companion.SableCompanion"

val isLoaded: Boolean = isPresent(COMPANION_CLASS)

val provider: SubLevelProvider = if (isLoaded) CompanionSubLevels else NoSubLevels

val flight: FlightSpace = if (isLoaded) SubLevelFlightSpace else FlatFlightSpace

val render: RenderSpace = if (isLoaded) SubLevelRenderSpace else FlatRenderSpace

private fun isPresent(className: String) = runCatching {
Class.forName(className, false, SableCompat::class.java.classLoader)
}.isSuccess
}

fun Level.subLevelId(pos: BlockPos): UUID? = SableCompat.provider.subLevelId(this, pos)

fun Level.toWorld(pos: Vec3): Vec3 = SableCompat.provider.toWorld(this, pos)

fun Level.toWorld(pos: BlockPos): Vec3 = toWorld(Vec3.atCenterOf(pos))

fun Level.worldDistanceSqr(a: Vec3, b: Vec3): Double = SableCompat.provider.worldDistanceSqr(this, a, b)

fun Level.worldDistanceSqr(a: BlockPos, b: BlockPos): Double =
worldDistanceSqr(Vec3.atCenterOf(a), Vec3.atCenterOf(b))
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.devin.cbbees.compat.sable

import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3

object SubLevelFlightSpace : FlightSpace {

override fun crossings(level: Level, positions: List<BlockPos>): Set<Int> {
val spaces = positions.map { CompanionSubLevels.subLevelId(level, it) }
return (1 until spaces.size).filter { spaces[it - 1] != spaces[it] }.map { it - 1 }.toSet()
}

override fun pose(from: BlockPos, to: BlockPos, t: Double, offset: Vec3, partialTick: Float): FlightPose {
val start = Vec3.atCenterOf(from)
val end = Vec3.atCenterOf(to)
val fromFrame = CompanionSubLevels.clientFrame(from, partialTick)
val toFrame = if (to == from) fromFrame else CompanionSubLevels.clientFrame(to, partialTick)

if (fromFrame?.id == toFrame?.id) {
val local = start.lerp(end, t).add(offset)
return FlightPose(fromFrame?.project(local) ?: local, fromFrame?.rotation)
}

val worldStart = fromFrame?.project(start) ?: start
val worldEnd = toFrame?.project(end) ?: end
return FlightPose(worldStart.lerp(worldEnd, t).add(offset), null)
}

override fun heading(from: BlockPos, to: BlockPos): Vec3 {
val start = Vec3.atCenterOf(from)
val end = Vec3.atCenterOf(to)
val fromFrame = CompanionSubLevels.clientFrame(from, 1f)
val toFrame = CompanionSubLevels.clientFrame(to, 1f)
if (fromFrame?.id == toFrame?.id) return end.subtract(start)
return (toFrame?.project(end) ?: end).subtract(fromFrame?.project(start) ?: start)
}
}
35 changes: 35 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/SubLevelFrame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.devin.cbbees.compat.sable

import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.world.phys.Vec3
import org.joml.Quaterniond
import org.joml.Quaternionf
import org.joml.Vector3d
import java.util.UUID

class SubLevelFrame(
val id: UUID,
private val position: Vector3d,
private val orientation: Quaterniond,
private val rotationPoint: Vector3d,
private val scale: Vector3d
) {
val rotation: Quaternionf get() = Quaternionf(orientation)

fun project(local: Vec3): Vec3 {
val world = orientation.transform(Vector3d(local.x, local.y, local.z).sub(rotationPoint).mul(scale)).add(position)
return Vec3(world.x, world.y, world.z)
}

fun unproject(world: Vec3): Vec3 {
val local = orientation.transformInverse(Vector3d(world.x, world.y, world.z).sub(position)).div(scale).add(rotationPoint)
return Vec3(local.x, local.y, local.z)
}

fun applyTo(poseStack: PoseStack, camera: Vec3): Vec3 {
poseStack.translate(position.x - camera.x, position.y - camera.y, position.z - camera.z)
poseStack.mulPose(rotation)
poseStack.scale(scale.x.toFloat(), scale.y.toFloat(), scale.z.toFloat())
return Vec3(rotationPoint.x, rotationPoint.y, rotationPoint.z)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package de.devin.cbbees.compat.sable

import de.devin.cbbees.CreateBuzzyBeez
import de.devin.cbbees.content.domain.beehive.PortableBeeHive
import de.devin.cbbees.content.domain.network.BeeNetwork
import de.devin.cbbees.content.domain.network.ServerBeeNetworkManager
import de.devin.cbbees.util.ServerSide
import net.minecraft.core.BlockPos
import net.neoforged.bus.api.SubscribeEvent
import net.neoforged.neoforge.event.tick.ServerTickEvent

@ServerSide
object SubLevelNetworkTracker {

private const val INTERVAL_TICKS = 10

@SubscribeEvent
@JvmStatic
fun onServerTick(event: ServerTickEvent.Post) {
if (event.server.tickCount % INTERVAL_TICKS != 0) return
val split = splitDisconnected()
val merged = mergeReconnected()
val adopted = adoptOrphans()
if (split || merged || adopted) ServerBeeNetworkManager.rebuildIndexes()
}

private fun splitDisconnected(): Boolean =
movingNetworks().map { network ->
ServerBeeNetworkManager.splitIfDisconnected(network).also { didSplit ->
if (didSplit) CreateBuzzyBeez.LOGGER.info("[NET] Network ${network.id} split after its sub-level moved")
}
}.any { it }

private fun mergeReconnected(): Boolean {
var merged = false
movingNetworks().forEach { network ->
if (network !in ServerBeeNetworkManager.getNetworks()) return@forEach
val anchors = network.components.filter { it.isAnchor() }
ServerBeeNetworkManager.getNetworks()
.filter { other -> other !== network && !other.isPortable() && anchors.any(other::canConnect) }
.forEach { other ->
ServerBeeNetworkManager.mergeNetworks(network, other)
CreateBuzzyBeez.LOGGER.info("[NET] Network ${other.id} merged into ${network.id} after a sub-level moved into range")
merged = true
}
}
return merged
}

private fun adoptOrphans(): Boolean =
movingNetworks().flatMap { network ->
network.components.filter { it.isAnchor() }.map { anchor ->
val center = BlockPos.containing(anchor.world.toWorld(anchor.pos))
ServerBeeNetworkManager.adoptOrphans(network, anchor.world, center, anchor.getNetworkingRange())
}
}.any { it }

private fun movingNetworks() = ServerBeeNetworkManager.getNetworks().filter { network ->
!network.isPortable() && network.components.any { it.world.subLevelId(it.pos) != null }
}

private fun BeeNetwork.isPortable() = components.any { it is PortableBeeHive }
}
18 changes: 18 additions & 0 deletions src/main/kotlin/de/devin/cbbees/compat/sable/SubLevelProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.devin.cbbees.compat.sable

import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.phys.Vec3
import java.util.UUID

interface SubLevelProvider {
fun subLevelId(level: Level, pos: BlockPos): UUID?
fun toWorld(level: Level, pos: Vec3): Vec3
fun worldDistanceSqr(level: Level, a: Vec3, b: Vec3): Double
}

object NoSubLevels : SubLevelProvider {
override fun subLevelId(level: Level, pos: BlockPos): UUID? = null
override fun toWorld(level: Level, pos: Vec3): Vec3 = pos
override fun worldDistanceSqr(level: Level, a: Vec3, b: Vec3): Double = a.distanceToSqr(b)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.devin.cbbees.compat.sable

import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.Vec3
import java.util.function.Consumer

object SubLevelRenderSpace : RenderSpace {

override fun draw(poseStack: PoseStack, anchor: BlockPos, camera: Vec3, partialTick: Float, render: Consumer<Vec3>) {
val frame = CompanionSubLevels.clientFrame(anchor, partialTick) ?: return render.accept(camera)
poseStack.pushPose()
render.accept(frame.applyTo(poseStack, camera))
poseStack.popPose()
}

override fun toLocal(anchor: BlockPos, world: Vec3, partialTick: Float): Vec3 =
CompanionSubLevels.clientFrame(anchor, partialTick)?.unproject(world) ?: world
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ object BeeWorldRenderer {
val time = System.nanoTime() / 1_000_000_000.0f

flightBees.forEach { bee ->
val pos = bee.lerpPos(partialTick)
val pose = bee.lerpPose(partialTick)
val pos = pose.position
val dx = pos.x - camPos.x
val dy = pos.y - camPos.y
val dz = pos.z - camPos.z
Expand All @@ -76,6 +77,7 @@ object BeeWorldRenderer {

poseStack.pushPose()
poseStack.translate(dx, dy, dz)
pose.rotation?.let(poseStack::mulPose)
poseStack.mulPose(Axis.YP.rotationDegrees(180f - bee.yRot()))

renderModel(poseStack, bufferSource, modelRes, textureRes, time)
Expand Down
Loading
Loading