that's a lotta changes
This commit is contained in:
parent
97d2ea5cdf
commit
32f3c692b8
53 changed files with 1362 additions and 271 deletions
|
|
@ -1,5 +1,6 @@
|
|||
// 1.19.2 2022-11-27T14:23:10.976127707 Models
|
||||
// 1.19.2 2023-04-29T18:02:19.599832005 Models
|
||||
0812a674d14cfc6fbb7c0e2ac1b473bf2afe1965 assets/dawd3/models/item/brown_patch_cable.json
|
||||
6e01a1aa07f3a36d7950a1b00d1bc6e9045b9995 assets/dawd3/blockstates/knob.json
|
||||
bf0e322e33123cb6873c2da4e8c6ab85688deb4e assets/dawd3/models/item/gray_patch_cable.json
|
||||
6a9fb209d82556f5941422a8d047a0ae2af1dc8f assets/dawd3/models/item/sine_oscillator.json
|
||||
215b221d48639e96de11914625a770a389d65b81 assets/dawd3/models/item/green_patch_cable.json
|
||||
|
|
@ -9,8 +10,8 @@ f7b47538f17992177e97e06842c0039ae5096b2b assets/dawd3/blockstates/speaker.json
|
|||
9cf2cff42345ec60a944d7399b5047323aa8e88c assets/dawd3/models/item/red_patch_cable.json
|
||||
8c6f0307320980a66c70622b0b7c72c8cfe78dc3 assets/dawd3/models/item/light_gray_patch_cable.json
|
||||
5ed33e9ec3c70e8cc027eea6425951d51487437f assets/dawd3/models/item/blue_patch_cable.json
|
||||
83866bdfd40b759257070558c9ccb942e082914a assets/dawd3/blockstates/fader.json
|
||||
a4e8bc89d39021eb8d56ad7735216cb851d67287 assets/dawd3/models/item/light_blue_patch_cable.json
|
||||
aa1a1807d2c46f1f25e3d0c507952fefb5b3cd9f assets/dawd3/models/item/knob.json
|
||||
0023521ecb90e79515f0fa0088609c70aac9a605 assets/dawd3/models/block/speaker.json
|
||||
8aa966337109315240614d5257eb72f959eba5d8 assets/dawd3/models/item/orange_patch_cable.json
|
||||
12c4bfd825b2476955afcd3bb23c1f736ce68caa assets/dawd3/models/item/yellow_patch_cable.json
|
||||
|
|
@ -19,8 +20,7 @@ aab1bce7ec4e7c7dccd3d33d4242de12b63a981d assets/dawd3/models/item/white_patch_ca
|
|||
e3c6aacd49a6395f37047d3df31f91a18a411267 assets/dawd3/models/item/speaker.json
|
||||
32bb0e6e3bf75b9005602e8fb1042ad5d41286ad assets/dawd3/models/item/lime_patch_cable.json
|
||||
956d8f117df95cf62c8cac375cff853df96840d6 assets/dawd3/models/item/pink_patch_cable.json
|
||||
71fd99012b4338600f0a11dcadb9a97a6e0a15e5 assets/dawd3/models/block/fader.json
|
||||
3b1811bab3ba394ba03b67ac2efc72cb35316dc8 assets/dawd3/blockstates/sine_oscillator.json
|
||||
9c18a8292a0c9990cd23bebf5c6191c2114ccc6d assets/dawd3/models/item/black_patch_cable.json
|
||||
d300f52d5aa2dcb2ec7d9a0cdb1138104e016d83 assets/dawd3/models/item/fader.json
|
||||
0d759623578e206f437f2d9976443a773721a9f3 assets/dawd3/models/block/sine_oscillator.json
|
||||
ff4e86bc6228d4ced17fc8df632bd9d3812a51d0 assets/dawd3/models/block/knob.json
|
||||
fb1d7e02f0cdd3807d828b0bec299c6533b9de57 assets/dawd3/models/block/sine_oscillator.json
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"variants": {
|
||||
"facing=east": {
|
||||
"model": "dawd3:block/fader",
|
||||
"y": 90
|
||||
},
|
||||
"facing=north": {
|
||||
"model": "dawd3:block/fader"
|
||||
},
|
||||
"facing=south": {
|
||||
"model": "dawd3:block/fader",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west": {
|
||||
"model": "dawd3:block/fader",
|
||||
"y": 270
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
{
|
||||
"parent": "block/block",
|
||||
"elements": [
|
||||
{
|
||||
"faces": {
|
||||
"down": {
|
||||
"cullface": "down",
|
||||
"texture": "#bottom"
|
||||
},
|
||||
"east": {
|
||||
"cullface": "east",
|
||||
"texture": "#right"
|
||||
},
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#front"
|
||||
},
|
||||
"south": {
|
||||
"cullface": "south",
|
||||
"texture": "#back"
|
||||
},
|
||||
"up": {
|
||||
"cullface": "up",
|
||||
"texture": "#top"
|
||||
},
|
||||
"west": {
|
||||
"cullface": "west",
|
||||
"texture": "#left"
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
16.0,
|
||||
8.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#port",
|
||||
"uv": [
|
||||
4.0,
|
||||
0.0,
|
||||
8.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
6.0,
|
||||
3.0,
|
||||
-0.01
|
||||
],
|
||||
"to": [
|
||||
10.0,
|
||||
7.0,
|
||||
0.01
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": {
|
||||
"back": "dawd3:block/fader_side",
|
||||
"bottom": "dawd3:block/fader_bottom",
|
||||
"front": "dawd3:block/fader_side",
|
||||
"left": "dawd3:block/fader_side",
|
||||
"particle": "dawd3:block/fader_side",
|
||||
"port": "dawd3:device/port",
|
||||
"right": "dawd3:block/fader_side",
|
||||
"top": "dawd3:block/fader_top"
|
||||
}
|
||||
}
|
||||
|
|
@ -39,30 +39,6 @@
|
|||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#port",
|
||||
"uv": [
|
||||
0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
9.0,
|
||||
6.0,
|
||||
-0.01
|
||||
],
|
||||
"to": [
|
||||
13.0,
|
||||
10.0,
|
||||
0.01
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
|
|
@ -86,6 +62,30 @@
|
|||
10.0,
|
||||
0.01
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#port",
|
||||
"uv": [
|
||||
0.0,
|
||||
0.0,
|
||||
4.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
9.0,
|
||||
6.0,
|
||||
-0.01
|
||||
],
|
||||
"to": [
|
||||
13.0,
|
||||
10.0,
|
||||
0.01
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": {
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"parent": "dawd3:block/fader"
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ object Mod : ModInitializer, ClientModInitializer {
|
|||
logger.info("hello, sound traveler! welcome to the dawd³ experience")
|
||||
Blocks.initialize()
|
||||
Items.registry.registerAll()
|
||||
Packets.registerServerReceivers()
|
||||
}
|
||||
|
||||
override fun onInitializeClient() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import net.liquidev.d3r.D3r
|
|||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.generator.GeneratorWithProcessingState
|
||||
import net.liquidev.dawd3.audio.generator.MixGenerator
|
||||
import net.liquidev.dawd3.audio.unit.Frequency
|
||||
|
||||
/** Audio system and common settings. */
|
||||
object Audio {
|
||||
|
|
@ -14,6 +15,8 @@ object Audio {
|
|||
const val sampleRateFInv = 1.0f / sampleRateF
|
||||
private const val bufferSize = 256
|
||||
|
||||
val a4 = Frequency(440f)
|
||||
|
||||
private val outputDeviceId: Int
|
||||
private val outputStreamId: Int
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package net.liquidev.dawd3.audio.device
|
||||
|
||||
/** Device that can process audio. */
|
||||
interface Device {
|
||||
fun process(sampleCount: Int, channels: Int)
|
||||
interface Device<C : ControlSet> {
|
||||
fun process(sampleCount: Int, channels: Int, controls: C)
|
||||
|
||||
fun visitInputPorts(visit: (InputPortName, InputPort) -> Unit) {}
|
||||
fun visitOutputPorts(visit: (OutputPortName, OutputPort) -> Unit) {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package net.liquidev.dawd3.audio.device
|
||||
|
||||
class DeviceInstance(val state: Device) {
|
||||
class DeviceInstance private constructor(val state: Device<ControlSet>, val controls: ControlSet) {
|
||||
val inputPortsByName = hashMapOf<PortName, InputPort>()
|
||||
val outputPortsByName = hashMapOf<PortName, OutputPort>()
|
||||
|
||||
|
|
@ -29,11 +29,18 @@ class DeviceInstance(val state: Device) {
|
|||
for ((_, port) in inputPortsByName) {
|
||||
port.connectedOutput?.owningDevice?.process(sampleCount, channels, processingState)
|
||||
}
|
||||
state.process(sampleCount, channels)
|
||||
state.process(sampleCount, channels, controls)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "DeviceInstance($state)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : ControlSet> create(state: Device<T>, controls: T) =
|
||||
// NOTE: We're erasing the type from concrete T to Controls, the cast is fine.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
DeviceInstance(state as Device<ControlSet>, controls)
|
||||
}
|
||||
}
|
||||
57
src/main/kotlin/net/liquidev/dawd3/audio/device/controls.kt
Normal file
57
src/main/kotlin/net/liquidev/dawd3/audio/device/controls.kt
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package net.liquidev.dawd3.audio.device
|
||||
|
||||
import net.minecraft.util.Identifier
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ControlName(parent: Identifier, name: String) {
|
||||
val id = idInDevice(parent, name)
|
||||
|
||||
init {
|
||||
registry[id] = this
|
||||
}
|
||||
|
||||
override fun toString() = id.toString()
|
||||
|
||||
companion object {
|
||||
private val registry = hashMapOf<Identifier, ControlName>()
|
||||
|
||||
fun fromString(name: String): ControlName? =
|
||||
registry[Identifier(name)]
|
||||
}
|
||||
}
|
||||
|
||||
data class ControlDescriptor(
|
||||
val name: ControlName,
|
||||
val initialValue: Float,
|
||||
) {
|
||||
constructor(
|
||||
parent: Identifier,
|
||||
name: String,
|
||||
initialValue: Float,
|
||||
) : this(ControlName(parent, name), initialValue)
|
||||
}
|
||||
|
||||
class Control(val descriptor: ControlDescriptor) {
|
||||
private val internalValue = AtomicInteger(descriptor.initialValue.toBits())
|
||||
var value: Float
|
||||
get() = Float.fromBits(internalValue.get())
|
||||
set(value) = internalValue.set(value.toBits())
|
||||
}
|
||||
|
||||
interface ControlSet {
|
||||
fun visitControls(visit: (ControlDescriptor, Control) -> Unit)
|
||||
}
|
||||
|
||||
object NoControls : ControlSet {
|
||||
override fun visitControls(visit: (ControlDescriptor, Control) -> Unit) {}
|
||||
}
|
||||
|
||||
class ControlMap(set: ControlSet) {
|
||||
private val map = hashMapOf<ControlName, Control>()
|
||||
|
||||
init {
|
||||
set.visitControls { controlDescriptor, control -> map[controlDescriptor.name] = control }
|
||||
}
|
||||
|
||||
operator fun get(name: ControlName): Control? = map[name]
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package net.liquidev.dawd3.audio.device
|
||||
|
||||
class ControlAlreadyExistsException(what: String) : Exception(what)
|
||||
|
||||
class NoSuchPortException(what: String) : Exception(what)
|
||||
|
||||
class PortAlreadyExistsException(what: String) : Exception(what)
|
||||
|
|
|
|||
6
src/main/kotlin/net/liquidev/dawd3/audio/device/ids.kt
Normal file
6
src/main/kotlin/net/liquidev/dawd3/audio/device/ids.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package net.liquidev.dawd3.audio.device
|
||||
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
internal fun idInDevice(deviceId: Identifier, name: String): Identifier =
|
||||
Identifier(deviceId.namespace, "${deviceId.path}/$name")
|
||||
|
|
@ -55,12 +55,8 @@ sealed interface PortName {
|
|||
registry[name.id] = name
|
||||
}
|
||||
|
||||
fun fromString(name: String): PortName? {
|
||||
return registry[Identifier(name)]
|
||||
}
|
||||
|
||||
internal fun idInDevice(deviceId: Identifier, name: String): Identifier =
|
||||
Identifier(deviceId.namespace, "${deviceId.path}/$name")
|
||||
fun fromString(name: String): PortName? =
|
||||
registry[Identifier(name)]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +70,7 @@ class InputPortName(override val id: Identifier) : PortName {
|
|||
constructor(
|
||||
parent: Identifier,
|
||||
name: String,
|
||||
) : this(PortName.idInDevice(parent, name))
|
||||
) : this(idInDevice(parent, name))
|
||||
|
||||
override fun toString(): String = id.toString()
|
||||
}
|
||||
|
|
@ -92,12 +88,12 @@ class OutputPortName private constructor(
|
|||
constructor(
|
||||
parent: Identifier,
|
||||
name: String,
|
||||
) : this(PortName.idInDevice(parent, name), instanceOf = null)
|
||||
) : this(idInDevice(parent, name), instanceOf = null)
|
||||
|
||||
override fun toString(): String = id.toString()
|
||||
|
||||
fun makeInstanced(instanceName: String) =
|
||||
OutputPortName(PortName.idInDevice(id, instanceName), instanceOf = this)
|
||||
OutputPortName(idInDevice(id, instanceName), instanceOf = this)
|
||||
|
||||
fun resolveInstance() = instanceOf ?: this
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
package net.liquidev.dawd3.audio.devices
|
||||
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.Device
|
||||
import net.liquidev.dawd3.audio.device.DeviceDescriptor
|
||||
import net.liquidev.dawd3.audio.device.OutputPort
|
||||
import net.liquidev.dawd3.audio.device.OutputPortName
|
||||
import net.liquidev.dawd3.audio.device.*
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
class ConstantDevice(var value: Float) : Device {
|
||||
class ConstantDevice : Device<ConstantDevice.Controls> {
|
||||
companion object : DeviceDescriptor {
|
||||
override val id = Identifier(Mod.id, "constant")
|
||||
val valueControl = ControlDescriptor(id, "value", 0f)
|
||||
val outputPort = OutputPortName(id, "output")
|
||||
}
|
||||
|
||||
class Controls : ControlSet {
|
||||
val value = Control(valueControl)
|
||||
|
||||
override fun visitControls(visit: (ControlDescriptor, Control) -> Unit) {
|
||||
visit(valueControl, value)
|
||||
}
|
||||
}
|
||||
|
||||
val output = OutputPort(bufferCount = 1)
|
||||
|
||||
override fun process(sampleCount: Int, channels: Int) {
|
||||
override fun process(sampleCount: Int, channels: Int, controls: Controls) {
|
||||
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
|
||||
for (i in 0 until sampleCount) {
|
||||
outputBuffer[i] = value
|
||||
outputBuffer[i] = controls.value.value
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package net.liquidev.dawd3.audio.devices
|
|||
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.Audio
|
||||
import net.liquidev.dawd3.audio.AudioBuffer
|
||||
import net.liquidev.dawd3.audio.device.*
|
||||
import net.liquidev.dawd3.audio.unit.VOct
|
||||
import net.minecraft.util.Identifier
|
||||
import kotlin.math.sin
|
||||
|
||||
private const val twoPi = 2.0f * kotlin.math.PI.toFloat()
|
||||
|
||||
class SineOscillatorDevice : Device {
|
||||
class SineOscillatorDevice : Device<NoControls> {
|
||||
companion object : DeviceDescriptor {
|
||||
override val id = Identifier(Mod.id, "sine_oscillator")
|
||||
val frequencyCVPort = InputPortName(id, "frequency_cv")
|
||||
|
|
@ -18,10 +20,16 @@ class SineOscillatorDevice : Device {
|
|||
private val frequencyCV = InputPort()
|
||||
private val output = OutputPort(bufferCount = 1)
|
||||
|
||||
private val frequencyBuffer = AudioBuffer()
|
||||
private var phase = 0.0f
|
||||
|
||||
override fun process(sampleCount: Int, channels: Int) {
|
||||
val frequencyBuffer = frequencyCV.getConnectedOutputBuffer(0, sampleCount)
|
||||
override fun process(sampleCount: Int, channels: Int, controls: NoControls) {
|
||||
val voctBuffer = frequencyCV.getConnectedOutputBuffer(0, sampleCount)
|
||||
val frequencyBuffer = frequencyBuffer.getOrReallocate(sampleCount)
|
||||
for (i in 0 until sampleCount) {
|
||||
frequencyBuffer[i] = VOct(voctBuffer[i]).toFrequency(Audio.a4).value
|
||||
}
|
||||
|
||||
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
|
||||
for (i in 0 until sampleCount) {
|
||||
val phaseStep = Audio.sampleRateFInv * frequencyBuffer[i]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package net.liquidev.dawd3.audio.devices
|
||||
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.Device
|
||||
import net.liquidev.dawd3.audio.device.DeviceDescriptor
|
||||
import net.liquidev.dawd3.audio.device.InputPort
|
||||
import net.liquidev.dawd3.audio.device.InputPortName
|
||||
import net.liquidev.dawd3.audio.device.*
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
class TerminalDevice : Device {
|
||||
class TerminalDevice : Device<NoControls> {
|
||||
companion object : DeviceDescriptor {
|
||||
override val id = Identifier(Mod.id, "terminal")
|
||||
val inputPort = InputPortName(id, "input")
|
||||
|
|
@ -15,7 +12,7 @@ class TerminalDevice : Device {
|
|||
|
||||
val input = InputPort()
|
||||
|
||||
override fun process(sampleCount: Int, channels: Int) {
|
||||
override fun process(sampleCount: Int, channels: Int, controls: NoControls) {
|
||||
// Terminals don't do any audio processing.
|
||||
// The output port connected to `input` is instead used by terminal block entities like
|
||||
// speakers.
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package net.liquidev.dawd3.audio.generator
|
|||
|
||||
import net.liquidev.dawd3.audio.Audio
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.audio.device.NoControls
|
||||
import net.liquidev.dawd3.audio.devices.TerminalDevice
|
||||
|
||||
/** Audio generator that evaluates a device graph. */
|
||||
class DeviceGraphGenerator : AudioGenerator() {
|
||||
private val terminalDeviceState = TerminalDevice()
|
||||
val terminalDevice = DeviceInstance(terminalDeviceState)
|
||||
val terminalDevice = DeviceInstance.create(terminalDeviceState, NoControls)
|
||||
|
||||
override fun generate(output: FloatArray, sampleCount: Int, channelCount: Int) {
|
||||
// TODO: Maybe passing in the static processingState here is not the cleanest way to go
|
||||
|
|
|
|||
12
src/main/kotlin/net/liquidev/dawd3/audio/unit/frequency.kt
Normal file
12
src/main/kotlin/net/liquidev/dawd3/audio/unit/frequency.kt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package net.liquidev.dawd3.audio.unit
|
||||
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
@JvmInline
|
||||
value class VOct(val value: Float) {
|
||||
fun toFrequency(a4: Frequency): Frequency = Frequency(a4.value * 2f.pow(value / 12f))
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class Frequency(val value: Float)
|
||||
|
|
@ -10,7 +10,7 @@ import net.liquidev.dawd3.block.device.AnyDeviceBlockDescriptor
|
|||
import net.liquidev.dawd3.block.device.DeviceBlock
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntityRenderer
|
||||
import net.liquidev.dawd3.block.devices.FaderBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.KnobBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.SineOscillatorBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.SpeakerBlockDescriptor
|
||||
import net.liquidev.dawd3.item.Items
|
||||
|
|
@ -61,7 +61,7 @@ object Blocks {
|
|||
// Device blocks
|
||||
val speaker = registerDeviceBlock(SpeakerBlockDescriptor)
|
||||
val sineOscillator = registerDeviceBlock(SineOscillatorBlockDescriptor)
|
||||
val fader = registerDeviceBlock(FaderBlockDescriptor)
|
||||
val knob = registerDeviceBlock(KnobBlockDescriptor)
|
||||
|
||||
fun initialize() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@ package net.liquidev.dawd3.block.device
|
|||
|
||||
import net.liquidev.dawd3.common.*
|
||||
import net.liquidev.dawd3.item.PatchCableItem
|
||||
import net.liquidev.dawd3.ui.Rack
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.block.entity.BlockEntity
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemPlacementContext
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Vec3f
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
|
|
@ -67,4 +74,39 @@ class DeviceBlock(private val descriptor: AnyDeviceBlockDescriptor) :
|
|||
val direction = HorizontalDirection.fromDirection(state[Properties.HORIZONTAL_FACING])!!
|
||||
return VoxelShapes.cuboid(outlineCuboids[direction.index])
|
||||
}
|
||||
|
||||
@Deprecated("do not call this function directly")
|
||||
override fun onUse(
|
||||
state: BlockState,
|
||||
world: World,
|
||||
pos: BlockPos,
|
||||
player: PlayerEntity,
|
||||
hand: Hand,
|
||||
hit: BlockHitResult,
|
||||
): ActionResult {
|
||||
val blockEntity =
|
||||
world.getBlockEntity(pos) as? DeviceBlockEntity ?: return ActionResult.PASS
|
||||
|
||||
// We have to check this event ahead of time (in addition to doing it in PatchCableItem)
|
||||
// because block interactions take priority over item interactions.
|
||||
val usedPortName = DeviceBlockInteractions.findUsedPort(hit, blockEntity.descriptor)
|
||||
// TODO: Right-clicking the port should pop out its patch cable.
|
||||
|
||||
if (usedPortName == null && world is ClientWorld && !player.isSneaking) {
|
||||
val rack = Rack(world, Rack.collectAdjacentDevices(world, pos))
|
||||
if (rack.hasOpenWindows()) {
|
||||
MinecraftClient.getInstance().setScreen(rack)
|
||||
return ActionResult.SUCCESS
|
||||
}
|
||||
}
|
||||
if (usedPortName != null && player.isSneaking) {
|
||||
return if (blockEntity.severConnectionsInPort(usedPortName)) {
|
||||
ActionResult.SUCCESS
|
||||
} else {
|
||||
ActionResult.PASS
|
||||
}
|
||||
}
|
||||
|
||||
return ActionResult.PASS
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,19 @@ package net.liquidev.dawd3.block.device
|
|||
|
||||
import FaceTextures
|
||||
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
||||
import net.liquidev.dawd3.audio.device.ControlSet
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.minecraft.block.AbstractBlock
|
||||
import net.minecraft.block.Material
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Box
|
||||
|
||||
typealias AnyDeviceBlockDescriptor = DeviceBlockDescriptor<DeviceBlockDescriptor.ClientState, Any>
|
||||
typealias AnyDeviceBlockDescriptor = DeviceBlockDescriptor<DeviceBlockDescriptor.ClientState, ControlSet>
|
||||
|
||||
interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out ServerState> {
|
||||
interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out Controls : ControlSet> {
|
||||
val id: Identifier
|
||||
|
||||
val blockSettings: AbstractBlock.Settings
|
||||
|
|
@ -29,11 +31,15 @@ interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out
|
|||
val faceTextures: FaceTextures
|
||||
get() = FaceTextures.withFrontAndSide { id }
|
||||
|
||||
fun onClientLoad(world: ClientWorld): CS
|
||||
fun initControls(): Controls
|
||||
|
||||
fun onClientLoad(controls: @UnsafeVariance Controls, world: ClientWorld): CS
|
||||
fun onClientUnload(state: @UnsafeVariance CS, world: ClientWorld) {}
|
||||
|
||||
interface ClientState {
|
||||
val logicalDevice: DeviceInstance
|
||||
}
|
||||
|
||||
fun openUI(controls: @UnsafeVariance Controls, x: Int, y: Int): Widget? = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,13 @@ package net.liquidev.dawd3.block.device
|
|||
|
||||
import net.fabricmc.fabric.api.`object`.builder.v1.block.entity.FabricBlockEntityTypeBuilder
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.Devices
|
||||
import net.liquidev.dawd3.audio.device.InputPortName
|
||||
import net.liquidev.dawd3.audio.device.OutputPortName
|
||||
import net.liquidev.dawd3.audio.device.PortName
|
||||
import net.liquidev.dawd3.audio.device.*
|
||||
import net.liquidev.dawd3.block.Blocks
|
||||
import net.liquidev.dawd3.block.entity.D3BlockEntity
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.entity.BlockEntityType
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtElement
|
||||
import net.minecraft.nbt.NbtHelper
|
||||
import net.minecraft.nbt.NbtList
|
||||
import net.minecraft.nbt.*
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
private typealias DeviceBlockFactory = FabricBlockEntityTypeBuilder.Factory<DeviceBlockEntity>
|
||||
|
|
@ -27,7 +21,8 @@ class DeviceBlockEntity(
|
|||
) : D3BlockEntity(type, blockPos, blockState) {
|
||||
|
||||
private var clientState: DeviceBlockDescriptor.ClientState? = null
|
||||
private var serverState: Any? = null
|
||||
val controls = descriptor.initControls()
|
||||
val controlMap = ControlMap(controls)
|
||||
|
||||
internal data class InputConnection(
|
||||
val blockPosition: BlockPos,
|
||||
|
|
@ -45,6 +40,8 @@ class DeviceBlockEntity(
|
|||
|
||||
/** NBT compound keys. */
|
||||
private object Nbt {
|
||||
const val controls = "controls"
|
||||
|
||||
const val inputConnections = "inputConnections"
|
||||
|
||||
object InputConnection {
|
||||
|
|
@ -65,6 +62,15 @@ class DeviceBlockEntity(
|
|||
override fun readNbt(nbt: NbtCompound) {
|
||||
super.readNbt(nbt)
|
||||
|
||||
val controlsNbt = nbt.getCompound(Nbt.controls)
|
||||
controls.visitControls { controlDescriptor, control ->
|
||||
val controlName = controlDescriptor.name.toString()
|
||||
if (controlName in controlsNbt) {
|
||||
val value = controlsNbt.getFloat(controlName)
|
||||
control.value = value
|
||||
}
|
||||
}
|
||||
|
||||
val inputConnectionsNbt =
|
||||
nbt.getList(Nbt.inputConnections, NbtElement.COMPOUND_TYPE.toInt())
|
||||
for (i in 0 until inputConnectionsNbt.size) {
|
||||
|
|
@ -112,6 +118,12 @@ class DeviceBlockEntity(
|
|||
override fun writeNbt(nbt: NbtCompound) {
|
||||
super.writeNbt(nbt)
|
||||
|
||||
val controlsNbt = NbtCompound()
|
||||
controls.visitControls { controlDescriptor, control ->
|
||||
controlsNbt.put(controlDescriptor.name.toString(), NbtFloat.of(control.value))
|
||||
}
|
||||
nbt.put(Nbt.controls, controlsNbt)
|
||||
|
||||
val inputConnectionsNbt = NbtList()
|
||||
for ((inputPortName, connection) in inputConnections) {
|
||||
val connectionNbt = NbtCompound()
|
||||
|
|
@ -140,7 +152,7 @@ class DeviceBlockEntity(
|
|||
}
|
||||
|
||||
override fun onClientLoad(world: ClientWorld) {
|
||||
clientState = descriptor.onClientLoad(world)
|
||||
clientState = descriptor.onClientLoad(controls, world)
|
||||
WorldDeviceLoading.enqueueDeviceForRebuild(this)
|
||||
}
|
||||
|
||||
|
|
@ -164,9 +176,37 @@ class DeviceBlockEntity(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun reapAllConnections() {
|
||||
// TODO: This should be triggered whenever a device block's destroyed and reap all
|
||||
// existing connections to the device.
|
||||
fun severConnectionsInPort(portName: PortName): Boolean {
|
||||
val world = world ?: return false
|
||||
|
||||
val clientState = clientState
|
||||
val severedConnections = if (clientState != null) {
|
||||
val resolvedPortName =
|
||||
if (portName is OutputPortName) portName.resolveInstance() else portName
|
||||
Devices.severAllConnectionsInPort(clientState.logicalDevice, resolvedPortName)
|
||||
} else 0
|
||||
|
||||
when (portName) {
|
||||
is InputPortName -> {
|
||||
val inputConnection = inputConnections.remove(portName)
|
||||
if (inputConnection != null) {
|
||||
val blockEntity = world.getBlockEntity(inputConnection.blockPosition)
|
||||
if (blockEntity is DeviceBlockEntity) {
|
||||
blockEntity.outputConnections.remove(inputConnection.outputPortName)
|
||||
}
|
||||
}
|
||||
}
|
||||
is OutputPortName -> {
|
||||
val blockPosition = outputConnections.remove(portName)
|
||||
if (blockPosition != null) {
|
||||
val blockEntity = world.getBlockEntity(blockPosition)
|
||||
if (blockEntity is DeviceBlockEntity) {
|
||||
blockEntity.inputConnections.values.removeAll { it.outputPortName == portName }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return severedConnections != 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ class DeviceBlockEntityRenderer(context: BlockEntityRendererFactory.Context) : B
|
|||
)
|
||||
val renderLayerFactory = Function<Identifier, RenderLayer> { renderLayer }
|
||||
|
||||
|
||||
const val cableProtrusionAmount = 0.69f // nice
|
||||
const val cableThickness = 0.03f
|
||||
const val cableSegmentCount = 6
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package net.liquidev.dawd3.block.device
|
||||
|
||||
import net.liquidev.dawd3.audio.device.PortName
|
||||
import net.liquidev.dawd3.common.*
|
||||
import net.liquidev.dawd3.datagen.device.DeviceBlockModel
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Vec2f
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
object DeviceBlockInteractions {
|
||||
fun findUsedPort(
|
||||
hit: BlockHitResult,
|
||||
descriptor: AnyDeviceBlockDescriptor,
|
||||
): PortName? {
|
||||
val horizontalSide = HorizontalDirection.fromDirection(hit.side) ?: return null
|
||||
val usePosition =
|
||||
calculateUsePositionOnHorizontalFace(hit.pos, hit.blockPos, horizontalSide)
|
||||
for ((portName, port) in descriptor.portLayout) {
|
||||
if (isUsedOnPort(port, usePosition)) {
|
||||
return portName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun calculateUsePositionOnHorizontalFace(
|
||||
hitPosition: Vec3d,
|
||||
blockPosition: BlockPos,
|
||||
side: HorizontalDirection,
|
||||
): Vec2f {
|
||||
val relativeHitPosition = (hitPosition - blockPosition.toVec3d()).toVec3f()
|
||||
val faceCorrection = side.faceCorrection
|
||||
return faceCorrection * (side.direction.to2DPlane * relativeHitPosition)
|
||||
}
|
||||
|
||||
private fun isUsedOnPort(
|
||||
port: PhysicalPort,
|
||||
usePosition: Vec2f,
|
||||
): Boolean {
|
||||
val usePositionInPixels = usePosition * Vec2f(16f, 16f)
|
||||
val portTopLeft = DeviceBlockModel.relativeToAbsolutePortPosition(port.position)
|
||||
val portBottomRight = portTopLeft + DeviceBlockModel.portSize
|
||||
return pointInRectangle(usePositionInPixels, portTopLeft, portBottomRight)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package net.liquidev.dawd3.block.devices
|
||||
|
||||
import FaceTextures
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.audio.devices.ConstantDevice
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
|
||||
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec2f
|
||||
|
||||
object FaderBlockDescriptor : DeviceBlockDescriptor<FaderBlockDescriptor.ClientState, Unit> {
|
||||
override val id = Identifier(Mod.id, "fader")
|
||||
|
||||
override val cuboid = Cuboids.halfBlock
|
||||
override val portLayout = PhysicalPort.layout {
|
||||
port(
|
||||
ConstantDevice.outputPort,
|
||||
instanceName = "front",
|
||||
position = Vec2f(0.5f, 0.75f),
|
||||
side = PhysicalPort.Side.Front,
|
||||
)
|
||||
}
|
||||
override val faceTextures = FaceTextures.withTopSideAndBottom { id }
|
||||
|
||||
class ClientState : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice = DeviceInstance(ConstantDevice(value = 440.0f))
|
||||
}
|
||||
|
||||
override fun onClientLoad(world: ClientWorld) = ClientState()
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package net.liquidev.dawd3.block.devices
|
||||
|
||||
import FaceTextures
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.audio.devices.ConstantDevice
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
|
||||
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Knob
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec2f
|
||||
|
||||
object KnobBlockDescriptor : DeviceBlockDescriptor<KnobBlockDescriptor.ClientState, ConstantDevice.Controls> {
|
||||
override val id = Identifier(Mod.id, "knob")
|
||||
|
||||
override val cuboid = Cuboids.fullBlock
|
||||
override val portLayout = PhysicalPort.layout {
|
||||
port(
|
||||
ConstantDevice.outputPort,
|
||||
instanceName = "front",
|
||||
position = Vec2f(0.5f, 0.8125f),
|
||||
side = PhysicalPort.Side.Front,
|
||||
)
|
||||
}
|
||||
override val faceTextures = FaceTextures.withFrontAndSide { id }
|
||||
|
||||
class ClientState(controls: ConstantDevice.Controls) : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice =
|
||||
DeviceInstance.create(ConstantDevice(), controls)
|
||||
}
|
||||
|
||||
override fun initControls() = ConstantDevice.Controls()
|
||||
|
||||
override fun onClientLoad(controls: ConstantDevice.Controls, world: ClientWorld) =
|
||||
ClientState(controls)
|
||||
|
||||
override fun openUI(controls: ConstantDevice.Controls, x: Int, y: Int): Widget =
|
||||
Window(x, y, 48, 48, Text.translatable("block.dawd3.knob")).apply {
|
||||
children.add(
|
||||
Knob(
|
||||
x = 14,
|
||||
y = 18,
|
||||
control = controls.value,
|
||||
min = -48f,
|
||||
max = 48f,
|
||||
color = Knob.Color.Blue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package net.liquidev.dawd3.block.devices
|
|||
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.audio.device.NoControls
|
||||
import net.liquidev.dawd3.audio.devices.SineOscillatorDevice
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
|
||||
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||
|
|
@ -9,7 +10,7 @@ import net.minecraft.client.world.ClientWorld
|
|||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec2f
|
||||
|
||||
object SineOscillatorBlockDescriptor : DeviceBlockDescriptor<SineOscillatorBlockDescriptor.ClientState, Unit> {
|
||||
object SineOscillatorBlockDescriptor : DeviceBlockDescriptor<SineOscillatorBlockDescriptor.ClientState, NoControls> {
|
||||
override val id = Identifier(Mod.id, "sine_oscillator")
|
||||
|
||||
override val portLayout = PhysicalPort.layout {
|
||||
|
|
@ -25,9 +26,11 @@ object SineOscillatorBlockDescriptor : DeviceBlockDescriptor<SineOscillatorBlock
|
|||
)
|
||||
}
|
||||
|
||||
class ClientState : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice = DeviceInstance(SineOscillatorDevice())
|
||||
class ClientState(controls: NoControls) : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice = DeviceInstance.create(SineOscillatorDevice(), controls)
|
||||
}
|
||||
|
||||
override fun onClientLoad(world: ClientWorld) = ClientState()
|
||||
override fun initControls() = NoControls
|
||||
|
||||
override fun onClientLoad(controls: NoControls, world: ClientWorld) = ClientState(controls)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
|||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.Audio
|
||||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.audio.device.NoControls
|
||||
import net.liquidev.dawd3.audio.devices.TerminalDevice
|
||||
import net.liquidev.dawd3.audio.generator.DeviceGraphGenerator
|
||||
import net.liquidev.dawd3.audio.generator.MixGenerator
|
||||
|
|
@ -15,7 +16,7 @@ import net.minecraft.sound.BlockSoundGroup
|
|||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec2f
|
||||
|
||||
object SpeakerBlockDescriptor : DeviceBlockDescriptor<SpeakerBlockDescriptor.ClientState, Unit> {
|
||||
object SpeakerBlockDescriptor : DeviceBlockDescriptor<SpeakerBlockDescriptor.ClientState, NoControls> {
|
||||
override val id = Identifier(Mod.id, "speaker")
|
||||
override val blockSettings = FabricBlockSettings
|
||||
.of(Material.WOOD)
|
||||
|
|
@ -38,7 +39,9 @@ object SpeakerBlockDescriptor : DeviceBlockDescriptor<SpeakerBlockDescriptor.Cli
|
|||
}
|
||||
}
|
||||
|
||||
override fun onClientLoad(world: ClientWorld) = ClientState()
|
||||
override fun initControls() = NoControls
|
||||
|
||||
override fun onClientLoad(controls: NoControls, world: ClientWorld) = ClientState()
|
||||
|
||||
override fun onClientUnload(state: ClientState, world: ClientWorld) {
|
||||
state.channel.stop()
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
package net.liquidev.dawd3.common
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.function.Function
|
||||
|
||||
class TaskQueue<T, R> {
|
||||
private val tasks = arrayListOf<Function<T, R>>()
|
||||
private val tasks = ConcurrentLinkedQueue<Function<T, R>>()
|
||||
|
||||
fun enqueue(task: Function<T, R>) {
|
||||
tasks.add(task)
|
||||
}
|
||||
|
||||
fun flush(argument: T) {
|
||||
for (task in tasks) {
|
||||
task.apply(argument)
|
||||
while (true) {
|
||||
val task = tasks.poll()
|
||||
if (task != null) {
|
||||
task.apply(argument)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
tasks.clear()
|
||||
}
|
||||
}
|
||||
6
src/main/kotlin/net/liquidev/dawd3/common/color.kt
Normal file
6
src/main/kotlin/net/liquidev/dawd3/common/color.kt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package net.liquidev.dawd3.common
|
||||
|
||||
fun rgba(r: Int, g: Int, b: Int, a: Int): Int =
|
||||
(a shl 24) or (r shl 16) or (g shl 8) or b
|
||||
|
||||
fun rgb(r: Int, g: Int, b: Int): Int = rgba(r, g, b, 255)
|
||||
|
|
@ -26,15 +26,6 @@ fun Vec3f.max(other: Vec3f): Vec3f = Vec3f(max(x, other.x), max(y, other.y), max
|
|||
val Vec3f.lengthSquared get() = x * x + y * y + z * z
|
||||
val Vec3f.length get() = sqrt(lengthSquared)
|
||||
|
||||
fun Vec3f.normalize() {
|
||||
val length = length
|
||||
if (length != 0f) {
|
||||
set(x / length, y / length, z / length)
|
||||
} else {
|
||||
set(0f, 0f, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Vec3d.plus(other: Vec3d): Vec3d = Vec3d(x + other.x, y + other.y, z + other.z)
|
||||
operator fun Vec3d.minus(other: Vec3d): Vec3d = Vec3d(x - other.x, y - other.y, z - other.z)
|
||||
operator fun Vec3d.times(other: Vec3d): Vec3d = Vec3d(x * other.x, y * other.y, z * other.z)
|
||||
|
|
@ -136,6 +127,12 @@ fun pointInRectangle(point: Vec2f, topLeft: Vec2f, bottomRight: Vec2f) =
|
|||
fun lerp(a: Float, b: Float, t: Float): Float =
|
||||
a + t * (b - a)
|
||||
|
||||
fun mapRange(value: Float, fromMin: Float, fromMax: Float, toMin: Float, toMax: Float): Float =
|
||||
toMin + (value - fromMin) / (fromMax - fromMin) * (toMax - toMin)
|
||||
|
||||
fun clamp(value: Float, min: Float, max: Float): Float =
|
||||
max(min(value, max), min)
|
||||
|
||||
object Cuboids {
|
||||
val fullBlock = Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
|
||||
val halfBlock = Box(0.0, 0.0, 0.0, 1.0, 0.5, 1.0)
|
||||
|
|
@ -145,3 +142,15 @@ object Cuboids {
|
|||
|
||||
val Box.fromF get() = Vec3f(minX.toFloat(), minY.toFloat(), minZ.toFloat())
|
||||
val Box.toF get() = Vec3f(maxX.toFloat(), maxY.toFloat(), maxZ.toFloat())
|
||||
|
||||
@JvmInline
|
||||
value class Radians(val value: Float) {
|
||||
fun toDegrees() =
|
||||
Degrees(value / PI.toFloat() * 180f)
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class Degrees(val value: Float) {
|
||||
fun toRadians() =
|
||||
Radians(value / 180f * PI.toFloat())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
|
|||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.PortName
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||
import net.liquidev.dawd3.common.*
|
||||
import net.liquidev.dawd3.datagen.device.DeviceBlockModel
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockInteractions
|
||||
import net.liquidev.dawd3.events.PlayerEvents
|
||||
import net.liquidev.dawd3.net.ConnectPorts
|
||||
import net.liquidev.dawd3.net.StartConnectingPorts
|
||||
|
|
@ -18,46 +16,23 @@ import net.minecraft.server.network.ServerPlayerEntity
|
|||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Vec2f
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
class PatchCableItem(settings: Settings, val color: Byte) : BasicItem(settings) {
|
||||
override fun useOnBlock(context: ItemUsageContext): ActionResult {
|
||||
val blockEntity = context.world.getBlockEntity(context.blockPos)
|
||||
if (!context.world.isClient && blockEntity is DeviceBlockEntity) {
|
||||
val side = HorizontalDirection.fromDirection(context.side)
|
||||
if (side != null) {
|
||||
val usePosition =
|
||||
calculateUsePositionOnHorizontalFace(context.hitPos, context.blockPos, side)
|
||||
for ((portName, port) in blockEntity.descriptor.portLayout) {
|
||||
if (isUsedOnPort(port, usePosition)) {
|
||||
useOnPort(context, portName)
|
||||
break
|
||||
}
|
||||
if (context.player?.isSneaking != true) {
|
||||
val blockEntity = context.world.getBlockEntity(context.blockPos)
|
||||
if (!context.world.isClient && blockEntity is DeviceBlockEntity) {
|
||||
val portName =
|
||||
DeviceBlockInteractions.findUsedPort(context.hitResult, blockEntity.descriptor)
|
||||
if (portName != null) {
|
||||
useOnPort(context, portName)
|
||||
}
|
||||
}
|
||||
return ActionResult.success(context.world.isClient)
|
||||
} else {
|
||||
return context.world.getBlockState(context.blockPos)
|
||||
.onUse(context.world, context.player, context.hand, context.hitResult)
|
||||
}
|
||||
return ActionResult.success(context.world.isClient)
|
||||
}
|
||||
|
||||
private fun calculateUsePositionOnHorizontalFace(
|
||||
hitPosition: Vec3d,
|
||||
blockPosition: BlockPos,
|
||||
side: HorizontalDirection,
|
||||
): Vec2f {
|
||||
val relativeHitPosition = (hitPosition - blockPosition.toVec3d()).toVec3f()
|
||||
val faceCorrection = side.faceCorrection
|
||||
return faceCorrection * (side.direction.to2DPlane * relativeHitPosition)
|
||||
}
|
||||
|
||||
private fun isUsedOnPort(
|
||||
port: PhysicalPort,
|
||||
usePosition: Vec2f,
|
||||
): Boolean {
|
||||
val usePositionInPixels = usePosition * Vec2f(16f, 16f)
|
||||
val portTopLeft = DeviceBlockModel.relativeToAbsolutePortPosition(port.position)
|
||||
val portBottomRight = portTopLeft + DeviceBlockModel.portSize
|
||||
return pointInRectangle(usePositionInPixels, portTopLeft, portBottomRight)
|
||||
}
|
||||
|
||||
private fun useOnPort(context: ItemUsageContext, portName: PortName) {
|
||||
|
|
|
|||
42
src/main/kotlin/net/liquidev/dawd3/net/ControlTweaked.kt
Normal file
42
src/main/kotlin/net/liquidev/dawd3/net/ControlTweaked.kt
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package net.liquidev.dawd3.net
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
/** C2S message that a block's save data needs to be marked dirty as a result of control tweaks. */
|
||||
data class ControlTweaked(val position: BlockPos) {
|
||||
companion object {
|
||||
val id = Identifier(Mod.id, "control_tweaked")
|
||||
|
||||
fun registerServerReceiver() {
|
||||
ServerPlayNetworking.registerGlobalReceiver(id) { server, player, _, buffer, _ ->
|
||||
val packet = deserialize(buffer)
|
||||
server.execute {
|
||||
val world = player.world
|
||||
if (world != null) {
|
||||
val blockEntity =
|
||||
world.getBlockEntity(packet.position) as? DeviceBlockEntity
|
||||
?: return@execute
|
||||
blockEntity.markDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserialize(buffer: PacketByteBuf) =
|
||||
ControlTweaked(
|
||||
position = buffer.readBlockPos(),
|
||||
)
|
||||
}
|
||||
|
||||
fun serialize(): PacketByteBuf {
|
||||
val buffer = PacketByteBufs.create()
|
||||
buffer.writeBlockPos(position)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
|
|
@ -4,5 +4,11 @@ object Packets {
|
|||
fun registerClientReceivers() {
|
||||
StartConnectingPorts.registerClientReceiver()
|
||||
ConnectPorts.registerClientReceiver()
|
||||
TweakControl.registerClientReceiver()
|
||||
}
|
||||
|
||||
fun registerServerReceivers() {
|
||||
TweakControl.registerServerReceiver()
|
||||
ControlTweaked.registerServerReceiver()
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,6 @@ class StartConnectingPorts(val blockPosition: BlockPos, val portName: String, va
|
|||
return
|
||||
}
|
||||
|
||||
println("[PLAYER] Started connecting ports $player")
|
||||
PatchCableItem.startConnecting(
|
||||
player,
|
||||
PatchCableItem.OngoingConnection(blockPosition, portName, color)
|
||||
|
|
|
|||
87
src/main/kotlin/net/liquidev/dawd3/net/TweakControl.kt
Normal file
87
src/main/kotlin/net/liquidev/dawd3/net/TweakControl.kt
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package net.liquidev.dawd3.net
|
||||
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.audio.device.ControlName
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
data class TweakControl(val position: BlockPos, val control: Identifier, val newValue: Float) {
|
||||
companion object {
|
||||
val id = Identifier(Mod.id, "tweak_control")
|
||||
|
||||
fun registerServerReceiver() {
|
||||
ServerPlayNetworking.registerGlobalReceiver(id) { server, player, _, buffer, _ ->
|
||||
// TODO: This should check for distance or something along those lines
|
||||
// to prevent cheating. We aren't currently performing any such checks because
|
||||
// it's kind of hard (as a rack may span tens of meters with many controls.)
|
||||
|
||||
val packet = deserialize(buffer)
|
||||
server.execute {
|
||||
val blockEntity =
|
||||
player.world.getBlockEntity(packet.position) as? DeviceBlockEntity
|
||||
?: return@execute
|
||||
val controlName =
|
||||
ControlName.fromString(packet.control.toString()) ?: return@execute
|
||||
val control = blockEntity.controlMap[controlName]
|
||||
control?.value = packet.newValue
|
||||
|
||||
val witnesses = PlayerLookup.tracking(blockEntity)
|
||||
for (witness in witnesses) {
|
||||
// The sending player is the person tweaking, we don't wanna overwrite their
|
||||
// changes spuriously if their connection is laggy.
|
||||
if (witness != player) {
|
||||
ServerPlayNetworking.send(
|
||||
witness,
|
||||
id,
|
||||
TweakControl(
|
||||
packet.position,
|
||||
packet.control,
|
||||
packet.newValue,
|
||||
).serialize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerClientReceiver() {
|
||||
ClientPlayNetworking.registerGlobalReceiver(id) { client, _, buffer, _ ->
|
||||
val packet = deserialize(buffer)
|
||||
client.execute {
|
||||
val world = client.world
|
||||
if (world != null) {
|
||||
val blockEntity =
|
||||
world.getBlockEntity(packet.position) as? DeviceBlockEntity
|
||||
?: return@execute
|
||||
val controlName =
|
||||
ControlName.fromString(packet.control.toString()) ?: return@execute
|
||||
val control = blockEntity.controlMap[controlName]
|
||||
control?.value = packet.newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserialize(buffer: PacketByteBuf) =
|
||||
TweakControl(
|
||||
position = buffer.readBlockPos(),
|
||||
control = buffer.readIdentifier(),
|
||||
newValue = buffer.readFloat(),
|
||||
)
|
||||
}
|
||||
|
||||
fun serialize(): PacketByteBuf {
|
||||
val buffer = PacketByteBufs.create()
|
||||
buffer.writeBlockPos(position)
|
||||
buffer.writeIdentifier(control)
|
||||
buffer.writeFloat(newValue)
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
8
src/main/kotlin/net/liquidev/dawd3/render/Atlas.kt
Normal file
8
src/main/kotlin/net/liquidev/dawd3/render/Atlas.kt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package net.liquidev.dawd3.render
|
||||
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
data class Atlas(
|
||||
val asset: Identifier,
|
||||
val size: Int,
|
||||
)
|
||||
8
src/main/kotlin/net/liquidev/dawd3/render/Icon.kt
Normal file
8
src/main/kotlin/net/liquidev/dawd3/render/Icon.kt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package net.liquidev.dawd3.render
|
||||
|
||||
data class Icon(
|
||||
val u: Int,
|
||||
val v: Int,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
)
|
||||
23
src/main/kotlin/net/liquidev/dawd3/render/NinePatch.kt
Normal file
23
src/main/kotlin/net/liquidev/dawd3/render/NinePatch.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package net.liquidev.dawd3.render
|
||||
|
||||
data class NinePatch(
|
||||
val u: Int,
|
||||
val v: Int,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val borderTop: Int,
|
||||
val borderBottom: Int,
|
||||
val borderLeft: Int,
|
||||
val borderRight: Int,
|
||||
) {
|
||||
constructor(u: Int, v: Int, width: Int, height: Int, border: Int) : this(
|
||||
u,
|
||||
v,
|
||||
width,
|
||||
height,
|
||||
border,
|
||||
border,
|
||||
border,
|
||||
border,
|
||||
)
|
||||
}
|
||||
305
src/main/kotlin/net/liquidev/dawd3/render/Render.kt
Normal file
305
src/main/kotlin/net/liquidev/dawd3/render/Render.kt
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
package net.liquidev.dawd3.render
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import net.liquidev.dawd3.common.Radians
|
||||
import net.liquidev.dawd3.common.lerp
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawableHelper
|
||||
import net.minecraft.client.render.GameRenderer
|
||||
import net.minecraft.client.render.Tessellator
|
||||
import net.minecraft.client.render.VertexFormat
|
||||
import net.minecraft.client.render.VertexFormats
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
object Render {
|
||||
fun icon(
|
||||
matrices: MatrixStack,
|
||||
x: Int,
|
||||
y: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
atlas: Atlas,
|
||||
icon: Icon,
|
||||
) {
|
||||
RenderSystem.setShaderTexture(0, atlas.asset)
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
icon.u.toFloat(),
|
||||
icon.v.toFloat(),
|
||||
icon.width,
|
||||
icon.height,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
}
|
||||
|
||||
fun icon(matrices: MatrixStack, x: Int, y: Int, atlas: Atlas, icon: Icon) {
|
||||
icon(matrices, x, y, icon.width, icon.height, atlas, icon)
|
||||
}
|
||||
|
||||
fun line(
|
||||
matrices: MatrixStack,
|
||||
x1: Float,
|
||||
y1: Float,
|
||||
x2: Float,
|
||||
y2: Float,
|
||||
thickness: Float,
|
||||
atlas: Atlas,
|
||||
textureStrip: TextureStrip,
|
||||
) {
|
||||
if (x1 == x2 && y1 == y2) return
|
||||
|
||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||
RenderSystem.setShaderTexture(0, atlas.asset)
|
||||
|
||||
val matrix = matrices.peek().positionMatrix
|
||||
val buffer = Tessellator.getInstance().buffer
|
||||
|
||||
val dx = x2 - x1
|
||||
val dy = y2 - y1
|
||||
val length = sqrt(dx * dx + dy * dy)
|
||||
val ndx = dx / length
|
||||
val ndy = dy / length
|
||||
|
||||
buffer.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_TEXTURE)
|
||||
|
||||
buffer.vertex(matrix, x2 - ndy * thickness, y2 + ndx * thickness, 0f)
|
||||
buffer.texture(textureStrip.u1 / atlas.size, textureStrip.v2 / atlas.size)
|
||||
buffer.next()
|
||||
|
||||
buffer.vertex(matrix, x2 + ndy * thickness, y2 - ndx * thickness, 0f)
|
||||
buffer.texture(textureStrip.u2 / atlas.size, textureStrip.v2 / atlas.size)
|
||||
buffer.next()
|
||||
|
||||
buffer.vertex(matrix, x1 - ndy * thickness, y1 + ndx * thickness, 0f)
|
||||
buffer.texture(textureStrip.u1 / atlas.size, textureStrip.v1 / atlas.size)
|
||||
buffer.next()
|
||||
|
||||
buffer.vertex(matrix, x1 + ndy * thickness, y1 - ndx * thickness, 0f)
|
||||
buffer.texture(textureStrip.u2 / atlas.size, textureStrip.v1 / atlas.size)
|
||||
buffer.next()
|
||||
|
||||
|
||||
Tessellator.getInstance().draw()
|
||||
}
|
||||
|
||||
fun arcOutline(
|
||||
matrices: MatrixStack,
|
||||
centerX: Float,
|
||||
centerY: Float,
|
||||
radius: Float,
|
||||
thickness: Float,
|
||||
startAngle: Radians,
|
||||
endAngle: Radians,
|
||||
vertexCount: Int,
|
||||
atlas: Atlas,
|
||||
textureStrip: TextureStrip,
|
||||
) {
|
||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||
RenderSystem.setShaderTexture(0, atlas.asset)
|
||||
|
||||
val matrix = matrices.peek().positionMatrix
|
||||
val buffer = Tessellator.getInstance().buffer
|
||||
|
||||
// delta is the angle between subdivisions of the circle.
|
||||
val delta = (endAngle.value - startAngle.value) / vertexCount
|
||||
// alpha is the isosceles trapezoids' acute angle, which we need to correct the thickness
|
||||
// and create a proper mitre joint.
|
||||
val alpha = (PI.toFloat() - delta) / 2
|
||||
val offset = thickness / sin(alpha)
|
||||
val innerRadius = radius - offset / 2
|
||||
|
||||
val cos = cos(delta)
|
||||
val sin = sin(delta)
|
||||
var angleX = cos(startAngle.value)
|
||||
var angleY = sin(startAngle.value)
|
||||
|
||||
val u1 = textureStrip.u1 / atlas.size
|
||||
val u2 = textureStrip.u2 / atlas.size
|
||||
|
||||
buffer.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_TEXTURE)
|
||||
for (i in 0..vertexCount) {
|
||||
val innerX = centerX + angleX * innerRadius
|
||||
val innerY = centerY + angleY * innerRadius
|
||||
val outerX = centerX + angleX * (innerRadius + offset)
|
||||
val outerY = centerY + angleY * (innerRadius + offset)
|
||||
|
||||
val t = i.toFloat() / vertexCount
|
||||
val v = lerp(textureStrip.v1, textureStrip.v2, t) / atlas.size
|
||||
|
||||
buffer.vertex(matrix, outerX, outerY, 0f)
|
||||
buffer.texture(u2, v)
|
||||
buffer.next()
|
||||
buffer.vertex(matrix, innerX, innerY, 0f)
|
||||
buffer.texture(u1, v)
|
||||
buffer.next()
|
||||
|
||||
val aX = angleX
|
||||
val aY = angleY
|
||||
angleX = aX * cos - aY * sin
|
||||
angleY = aX * sin + aY * cos
|
||||
}
|
||||
Tessellator.getInstance().draw()
|
||||
}
|
||||
|
||||
fun ninePatch(
|
||||
matrices: MatrixStack,
|
||||
x: Int,
|
||||
y: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
atlas: Atlas,
|
||||
ninePatch: NinePatch,
|
||||
) {
|
||||
RenderSystem.setShaderTexture(0, atlas.asset)
|
||||
|
||||
// Top left
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x,
|
||||
y,
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.borderTop,
|
||||
ninePatch.u.toFloat(),
|
||||
ninePatch.v.toFloat(),
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.borderTop,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Top center
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + ninePatch.borderLeft,
|
||||
y,
|
||||
width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
ninePatch.borderTop,
|
||||
(ninePatch.u + ninePatch.borderLeft).toFloat(),
|
||||
ninePatch.v.toFloat(),
|
||||
ninePatch.width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
ninePatch.borderTop,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Top right
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + width - ninePatch.borderRight,
|
||||
y,
|
||||
ninePatch.borderRight,
|
||||
ninePatch.borderTop,
|
||||
(ninePatch.u + ninePatch.width - ninePatch.borderRight).toFloat(),
|
||||
ninePatch.v.toFloat(),
|
||||
ninePatch.borderRight,
|
||||
ninePatch.borderTop,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Middle left
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x,
|
||||
y + ninePatch.borderTop,
|
||||
ninePatch.borderLeft,
|
||||
height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
ninePatch.u.toFloat(),
|
||||
(ninePatch.v + ninePatch.borderTop).toFloat(),
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Center
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + ninePatch.borderLeft,
|
||||
y + ninePatch.borderTop,
|
||||
width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
(ninePatch.u + ninePatch.borderLeft).toFloat(),
|
||||
(ninePatch.v + ninePatch.borderTop).toFloat(),
|
||||
ninePatch.width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
ninePatch.height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Middle right
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + width - ninePatch.borderRight,
|
||||
y + ninePatch.borderTop,
|
||||
ninePatch.borderLeft,
|
||||
height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
(ninePatch.u + ninePatch.width - ninePatch.borderRight).toFloat(),
|
||||
(ninePatch.v + ninePatch.borderTop).toFloat(),
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.height - ninePatch.borderTop - ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Bottom left
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x,
|
||||
y + height - ninePatch.borderBottom,
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.borderBottom,
|
||||
ninePatch.u.toFloat(),
|
||||
(ninePatch.v + ninePatch.height - ninePatch.borderBottom).toFloat(),
|
||||
ninePatch.borderLeft,
|
||||
ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Bottom center
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + ninePatch.borderLeft,
|
||||
y + height - ninePatch.borderBottom,
|
||||
width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
ninePatch.borderBottom,
|
||||
(ninePatch.u + ninePatch.borderLeft).toFloat(),
|
||||
(ninePatch.v + ninePatch.height - ninePatch.borderBottom).toFloat(),
|
||||
ninePatch.width - ninePatch.borderLeft - ninePatch.borderRight,
|
||||
ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
// Bottom right
|
||||
DrawableHelper.drawTexture(
|
||||
matrices,
|
||||
x + width - ninePatch.borderRight,
|
||||
y + height - ninePatch.borderBottom,
|
||||
ninePatch.borderRight,
|
||||
ninePatch.borderBottom,
|
||||
(ninePatch.u + ninePatch.width - ninePatch.borderRight).toFloat(),
|
||||
(ninePatch.v + ninePatch.height - ninePatch.borderBottom).toFloat(),
|
||||
ninePatch.borderRight,
|
||||
ninePatch.borderBottom,
|
||||
atlas.size,
|
||||
atlas.size
|
||||
)
|
||||
}
|
||||
|
||||
fun textCentered(matrices: MatrixStack, centerX: Int, y: Int, text: Text, color: Int) {
|
||||
val textRenderer = MinecraftClient.getInstance().textRenderer
|
||||
val textWidth = textRenderer.getWidth(text)
|
||||
val x = centerX - textWidth / 2
|
||||
textRenderer.draw(
|
||||
matrices,
|
||||
text,
|
||||
x.toFloat(),
|
||||
y.toFloat(),
|
||||
color
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package net.liquidev.dawd3.render
|
||||
|
||||
/** Vertical texture strip. */
|
||||
data class TextureStrip(val u1: Float, val v1: Float, val u2: Float, val v2: Float)
|
||||
|
|
@ -6,18 +6,19 @@ import net.minecraft.screen.PlayerScreenHandler
|
|||
import net.minecraft.util.Identifier
|
||||
|
||||
object Textures {
|
||||
|
||||
/**
|
||||
* The set of textures that are not referenced by models but need to be loaded into the
|
||||
* block atlas.
|
||||
*/
|
||||
private val nonModel = arrayOf(
|
||||
private val nonModelBlockTextures = arrayOf(
|
||||
Identifier(Mod.id, "device/cable"),
|
||||
)
|
||||
|
||||
fun initializeClient() {
|
||||
ClientSpriteRegistryCallback.event(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE)
|
||||
.register { _, registry ->
|
||||
for (id in nonModel) {
|
||||
for (id in nonModelBlockTextures) {
|
||||
registry.register(id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
128
src/main/kotlin/net/liquidev/dawd3/ui/Rack.kt
Normal file
128
src/main/kotlin/net/liquidev/dawd3/ui/Rack.kt
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package net.liquidev.dawd3.ui
|
||||
|
||||
import net.fabricmc.api.EnvType
|
||||
import net.fabricmc.api.Environment
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.liquidev.dawd3.render.Atlas
|
||||
import net.liquidev.dawd3.render.Icon
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
class Rack(
|
||||
val world: ClientWorld,
|
||||
shownDevices: Iterable<BlockPos>,
|
||||
) : Screen(Text.translatable("screen.dawd3.rack.title")) {
|
||||
private data class OpenWidget(
|
||||
val blockPosition: BlockPos,
|
||||
val widget: Widget,
|
||||
)
|
||||
|
||||
private var openWidgets = run {
|
||||
var windowX = 32
|
||||
ArrayList(shownDevices.mapNotNull { blockPosition ->
|
||||
val blockEntity = world.getBlockEntity(blockPosition) as DeviceBlockEntity
|
||||
blockEntity.descriptor.openUI(blockEntity.controls, windowX, 32)
|
||||
?.let { widget ->
|
||||
windowX += widget.width + 8
|
||||
OpenWidget(blockPosition, widget)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun hasOpenWindows(): Boolean = openWidgets.isNotEmpty()
|
||||
|
||||
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
renderBackground(matrices)
|
||||
|
||||
// Handle blocks being destroyed behind our back.
|
||||
openWidgets.removeIf { world.getBlockEntity(it.blockPosition) !is DeviceBlockEntity }
|
||||
if (openWidgets.isEmpty()) {
|
||||
close()
|
||||
}
|
||||
|
||||
for (openWidget in openWidgets) {
|
||||
openWidget.widget.draw(matrices, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
Render.icon(matrices, 8, 8, badge.width * 2, badge.height * 2, atlas, badge)
|
||||
}
|
||||
|
||||
private fun propagateEvent(event: Event): Boolean {
|
||||
for (openWidget in openWidgets) {
|
||||
if (openWidget.widget.event(
|
||||
EventContext(world, openWidget.blockPosition),
|
||||
event.relativeTo(
|
||||
openWidget.widget.x.toDouble(),
|
||||
openWidget.widget.y.toDouble(),
|
||||
)
|
||||
) == null
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun mouseMoved(mouseX: Double, mouseY: Double) {
|
||||
propagateEvent(MouseMove(mouseX, mouseY))
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean =
|
||||
propagateEvent(MouseButton(Action.Down, mouseX, mouseY, button))
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean =
|
||||
propagateEvent(MouseButton(Action.Up, mouseX, mouseY, button))
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
return if (super.keyPressed(keyCode, scanCode, modifiers)) {
|
||||
true
|
||||
} else if (client?.options?.inventoryKey?.matchesKey(keyCode, scanCode) == true) {
|
||||
// Handle the player pressing 'E' to close the rack, since even though it is not an
|
||||
// inventory, it's just more intuitive that way.
|
||||
close()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldPause(): Boolean = false
|
||||
|
||||
companion object {
|
||||
val atlas = Atlas(asset = Identifier(Mod.id, "textures/ui/rack.png"), size = 64)
|
||||
val badge = Icon(u = 0, v = 16, width = 6, height = 3)
|
||||
|
||||
// TODO: This "adjacent device" system is kind of janky but it allows for pretty nice
|
||||
// organization of your devices into multiple racks of sorts. Maybe in the future we can
|
||||
// think of collecting non-adjacent devices as well.
|
||||
|
||||
fun collectAdjacentDevices(world: World, position: BlockPos): HashSet<BlockPos> {
|
||||
val result = hashSetOf<BlockPos>()
|
||||
collectAdjacentDevicesRec(result, world, position)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun collectAdjacentDevicesRec(
|
||||
outBlockPositions: HashSet<BlockPos>,
|
||||
world: World,
|
||||
position: BlockPos,
|
||||
) {
|
||||
if (position !in outBlockPositions && world.getBlockEntity(position) is DeviceBlockEntity) {
|
||||
outBlockPositions.add(position)
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.up())
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.down())
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.north())
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.south())
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.east())
|
||||
collectAdjacentDevicesRec(outBlockPositions, world, position.west())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/main/kotlin/net/liquidev/dawd3/ui/events.kt
Normal file
51
src/main/kotlin/net/liquidev/dawd3/ui/events.kt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package net.liquidev.dawd3.ui
|
||||
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
|
||||
sealed interface Event {
|
||||
fun relativeTo(x: Double, y: Double): Event
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
Down,
|
||||
Up,
|
||||
}
|
||||
|
||||
data class MouseButton(
|
||||
val action: Action,
|
||||
val mouseX: Double,
|
||||
val mouseY: Double,
|
||||
val absoluteMouseX: Double,
|
||||
val absoluteMouseY: Double,
|
||||
val button: Int,
|
||||
) : Event {
|
||||
constructor(action: Action, mouseX: Double, mouseY: Double, button: Int) : this(
|
||||
action,
|
||||
mouseX,
|
||||
mouseY,
|
||||
mouseX,
|
||||
mouseY,
|
||||
button
|
||||
)
|
||||
|
||||
override fun relativeTo(x: Double, y: Double) =
|
||||
MouseButton(action, mouseX - x, mouseY - y, absoluteMouseX, absoluteMouseY, button)
|
||||
}
|
||||
|
||||
data class MouseMove(
|
||||
val mouseX: Double,
|
||||
val mouseY: Double,
|
||||
val absoluteMouseX: Double,
|
||||
val absoluteMouseY: Double,
|
||||
) : Event {
|
||||
constructor(mouseX: Double, mouseY: Double) : this(mouseX, mouseY, mouseX, mouseY)
|
||||
|
||||
override fun relativeTo(x: Double, y: Double): Event =
|
||||
MouseMove(mouseX - x, mouseY - y, absoluteMouseX, absoluteMouseY)
|
||||
}
|
||||
|
||||
class EventContext(
|
||||
val world: ClientWorld,
|
||||
val blockPosition: BlockPos,
|
||||
)
|
||||
174
src/main/kotlin/net/liquidev/dawd3/ui/widget/Knob.kt
Normal file
174
src/main/kotlin/net/liquidev/dawd3/ui/widget/Knob.kt
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package net.liquidev.dawd3.ui.widget
|
||||
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
|
||||
import net.liquidev.dawd3.audio.device.Control
|
||||
import net.liquidev.dawd3.common.Degrees
|
||||
import net.liquidev.dawd3.common.Radians
|
||||
import net.liquidev.dawd3.common.clamp
|
||||
import net.liquidev.dawd3.common.mapRange
|
||||
import net.liquidev.dawd3.net.ControlTweaked
|
||||
import net.liquidev.dawd3.net.TweakControl
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.render.TextureStrip
|
||||
import net.liquidev.dawd3.ui.*
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.util.InputUtil
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
|
||||
class Knob(
|
||||
x: Int,
|
||||
y: Int,
|
||||
val control: Control,
|
||||
val min: Float,
|
||||
val max: Float,
|
||||
val color: Color,
|
||||
val sensitivity: Float = 0.25f,
|
||||
) : Widget(x, y) {
|
||||
override val width = 20
|
||||
override val height = 20
|
||||
|
||||
private data class DraggingInfo(var previousMouseY: Double)
|
||||
|
||||
private var draggingInfo: DraggingInfo? = null
|
||||
|
||||
override fun drawContent(matrices: MatrixStack, mouseX: Int, mouseY: Int, deltaTime: Float) {
|
||||
val centerX = width.toFloat() / 2
|
||||
val centerY = height.toFloat() / 2
|
||||
val radius = width.toFloat() / 2
|
||||
|
||||
val valueAngle = mapRange(control.value, min, max, startAngle.value, endAngle.value)
|
||||
val zeroAngle = clamp(
|
||||
mapRange(0f, min, max, startAngle.value, endAngle.value),
|
||||
startAngle.value,
|
||||
endAngle.value
|
||||
)
|
||||
|
||||
Render.arcOutline(
|
||||
matrices,
|
||||
centerX,
|
||||
centerY,
|
||||
radius - lineThickness * 0.75f,
|
||||
lineThickness * 0.75f,
|
||||
Radians(min(valueAngle, zeroAngle)),
|
||||
Radians(max(valueAngle, zeroAngle)),
|
||||
vertexCount = 16,
|
||||
Rack.atlas,
|
||||
coloredStrip(color)
|
||||
)
|
||||
Render.arcOutline(
|
||||
matrices,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
lineThickness,
|
||||
startAngle,
|
||||
endAngle,
|
||||
vertexCount = 16,
|
||||
Rack.atlas,
|
||||
blackStrip
|
||||
)
|
||||
Render.line(
|
||||
matrices,
|
||||
centerX + cos(valueAngle) * (radius * 0.25f),
|
||||
centerY + sin(valueAngle) * (radius * 0.25f),
|
||||
centerX + cos(valueAngle) * radius,
|
||||
centerY + sin(valueAngle) * radius,
|
||||
lineThickness / 2,
|
||||
Rack.atlas,
|
||||
blackStrip
|
||||
)
|
||||
}
|
||||
|
||||
override fun event(context: EventContext, event: Event): Event? {
|
||||
val client = MinecraftClient.getInstance()
|
||||
when (event) {
|
||||
is MouseButton -> if (event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
|
||||
when (event.action) {
|
||||
Action.Down -> if (
|
||||
containsRelativePoint(
|
||||
event.mouseX.toInt(),
|
||||
event.mouseY.toInt()
|
||||
)
|
||||
) {
|
||||
draggingInfo = DraggingInfo(event.absoluteMouseY)
|
||||
val guiScale =
|
||||
client.options.guiScale.value.toDouble()
|
||||
InputUtil.setCursorParameters(
|
||||
client.window.handle,
|
||||
GLFW.GLFW_CURSOR_DISABLED,
|
||||
event.absoluteMouseX * guiScale,
|
||||
event.absoluteMouseY * guiScale,
|
||||
)
|
||||
return null
|
||||
}
|
||||
Action.Up -> {
|
||||
val draggingInfo = draggingInfo
|
||||
if (draggingInfo != null) {
|
||||
InputUtil.setCursorParameters(
|
||||
client.window.handle,
|
||||
GLFW.GLFW_CURSOR_NORMAL,
|
||||
0.0,
|
||||
0.0,
|
||||
)
|
||||
ClientPlayNetworking.send(
|
||||
ControlTweaked.id,
|
||||
ControlTweaked(context.blockPosition).serialize()
|
||||
)
|
||||
this.draggingInfo = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is MouseMove -> {
|
||||
val draggingInfo = draggingInfo
|
||||
if (draggingInfo != null) {
|
||||
val guiScale = client.options.guiScale.value.toFloat()
|
||||
val deltaY = (draggingInfo.previousMouseY - event.absoluteMouseY) * guiScale
|
||||
ClientPlayNetworking.send(
|
||||
TweakControl.id,
|
||||
TweakControl(
|
||||
context.blockPosition,
|
||||
control.descriptor.name.id,
|
||||
newValue = alterValue(control.value, by = deltaY.toFloat()),
|
||||
).serialize()
|
||||
)
|
||||
// Reflect the change locally immediately for lower latency.
|
||||
control.value = alterValue(control.value, by = deltaY.toFloat())
|
||||
draggingInfo.previousMouseY = event.absoluteMouseY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
private fun alterValue(value: Float, by: Float): Float =
|
||||
max(min(value + by * sensitivity, max), min)
|
||||
|
||||
enum class Color(val index: Int) {
|
||||
Red(0),
|
||||
Orange(1),
|
||||
Yellow(2),
|
||||
Green(3),
|
||||
Blue(4),
|
||||
Purple(5),
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val startAngle = Degrees(135f).toRadians()
|
||||
private val endAngle = Degrees(405f).toRadians()
|
||||
|
||||
private const val lineThickness = 2f
|
||||
|
||||
private val blackStrip = TextureStrip(16f, 16f, 16f, 32f)
|
||||
private fun coloredStrip(color: Color): TextureStrip {
|
||||
val u = 18f + color.index.toFloat()
|
||||
return TextureStrip(u, 16f, u, 32f)
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/main/kotlin/net/liquidev/dawd3/ui/widget/Widget.kt
Normal file
50
src/main/kotlin/net/liquidev/dawd3/ui/widget/Widget.kt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package net.liquidev.dawd3.ui.widget
|
||||
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.EventContext
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
||||
abstract class Widget(var x: Int, var y: Int) {
|
||||
abstract val width: Int
|
||||
abstract val height: Int
|
||||
|
||||
protected abstract fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
deltaTime: Float,
|
||||
)
|
||||
|
||||
/** Returns non-null to propagate the event, or null to consume it. */
|
||||
abstract fun event(context: EventContext, event: Event): Event?
|
||||
|
||||
fun draw(matrices: MatrixStack, mouseX: Int, mouseY: Int, deltaTime: Float) {
|
||||
matrices.push()
|
||||
matrices.translate(x.toDouble(), y.toDouble(), 0.0)
|
||||
drawContent(matrices, mouseX, mouseY, deltaTime)
|
||||
matrices.pop()
|
||||
}
|
||||
|
||||
fun containsRelativePoint(x: Int, y: Int) =
|
||||
x >= 0 && y >= 0 && x <= width && y <= height
|
||||
|
||||
companion object {
|
||||
/** Propagates the event through the given iterable, returns whether it was consumed in the end. */
|
||||
fun propagateEvent(
|
||||
context: EventContext,
|
||||
event: Event,
|
||||
through: Iterable<Widget>,
|
||||
): Boolean {
|
||||
for (widget in through) {
|
||||
if (widget.event(
|
||||
context,
|
||||
event.relativeTo(widget.x.toDouble(), widget.y.toDouble())
|
||||
) == null
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/main/kotlin/net/liquidev/dawd3/ui/widget/Window.kt
Normal file
49
src/main/kotlin/net/liquidev/dawd3/ui/widget/Window.kt
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package net.liquidev.dawd3.ui.widget
|
||||
|
||||
import net.liquidev.dawd3.render.NinePatch
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.EventContext
|
||||
import net.liquidev.dawd3.ui.Rack
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
|
||||
class Window(x: Int, y: Int, override val width: Int, override val height: Int, val title: Text) :
|
||||
Widget(x, y) {
|
||||
|
||||
val children = mutableListOf<Widget>()
|
||||
|
||||
override fun drawContent(matrices: MatrixStack, mouseX: Int, mouseY: Int, deltaTime: Float) {
|
||||
Render.ninePatch(matrices, 2, 2, width, height, Rack.atlas, windowShadow)
|
||||
Render.ninePatch(matrices, 0, 0, width, height, Rack.atlas, windowBackground)
|
||||
|
||||
Render.textCentered(
|
||||
matrices,
|
||||
width / 2,
|
||||
4,
|
||||
title,
|
||||
0x111111
|
||||
)
|
||||
|
||||
for (child in children) {
|
||||
child.draw(matrices, mouseX, mouseY, deltaTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun event(context: EventContext, event: Event): Event? {
|
||||
return if (propagateEvent(context, event, children)) null else event
|
||||
}
|
||||
|
||||
companion object {
|
||||
val windowBackground = NinePatch(
|
||||
u = 0, v = 0,
|
||||
width = 16, height = 16,
|
||||
border = 3
|
||||
)
|
||||
val windowShadow = NinePatch(
|
||||
u = 16, v = 0,
|
||||
width = 16, height = 16,
|
||||
border = 3
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,5 +18,6 @@
|
|||
"item.dawd3.black_patch_cable": "Black Patch Cable",
|
||||
"block.dawd3.speaker": "Speaker",
|
||||
"block.dawd3.sine_oscillator": "Sine Oscillator",
|
||||
"block.dawd3.fader": "Fader"
|
||||
"block.dawd3.knob": "Knob",
|
||||
"screen.dawd3.rack.title": "Rack"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 143 B |
Binary file not shown.
|
Before Width: | Height: | Size: 141 B |
Binary file not shown.
|
Before Width: | Height: | Size: 237 B |
Binary file not shown.
|
Before Width: | Height: | Size: 228 B |
|
|
@ -1,5 +1,7 @@
|
|||
accessWidener v1 named
|
||||
|
||||
accessible method net/minecraft/item/ItemUsageContext getHitResult ()Lnet/minecraft/util/hit/BlockHitResult;
|
||||
|
||||
# The functionality for creating render layers is inaccessible by default for some reason and we need it to create a layer for rendering cables.
|
||||
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
|
||||
accessible class net/minecraft/client/render/RenderLayer$MultiPhase
|
||||
|
|
@ -11,4 +13,4 @@ accessible method net/minecraft/client/render/RenderLayer of (Ljava/lang/String;
|
|||
accessible field net/minecraft/client/render/RenderPhase ENABLE_LIGHTMAP Lnet/minecraft/client/render/RenderPhase$Lightmap;
|
||||
accessible field net/minecraft/client/render/RenderPhase SOLID_SHADER Lnet/minecraft/client/render/RenderPhase$Shader;
|
||||
accessible field net/minecraft/client/render/RenderPhase BLOCK_ATLAS_TEXTURE Lnet/minecraft/client/render/RenderPhase$Texture;
|
||||
accessible field net/minecraft/client/render/RenderPhase DISABLE_CULLING Lnet/minecraft/client/render/RenderPhase$Cull;
|
||||
accessible field net/minecraft/client/render/RenderPhase DISABLE_CULLING Lnet/minecraft/client/render/RenderPhase$Cull;
|
||||
Reference in a new issue