phase
This commit is contained in:
parent
015e3fb8d7
commit
152ffda473
18 changed files with 174 additions and 80 deletions
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
|||
// 1.19.2 2023-04-29T18:02:19.599832005 Models
|
||||
// 1.19.2 2023-05-01T14:06:55.653907421 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
|
||||
|
|
@ -8,19 +8,22 @@ e34001d3c974aecfa347d435beb6bc0b9d325897 assets/dawd3/models/item/cyan_patch_cab
|
|||
8ba890b28c5ac57c59f19ccc8c72825caac10677 assets/dawd3/models/item/magenta_patch_cable.json
|
||||
f7b47538f17992177e97e06842c0039ae5096b2b assets/dawd3/blockstates/speaker.json
|
||||
9cf2cff42345ec60a944d7399b5047323aa8e88c assets/dawd3/models/item/red_patch_cable.json
|
||||
0fb5cd695c2a82a2353809529dc8b5086ee2c87d assets/dawd3/blockstates/phase.json
|
||||
8c6f0307320980a66c70622b0b7c72c8cfe78dc3 assets/dawd3/models/item/light_gray_patch_cable.json
|
||||
5ed33e9ec3c70e8cc027eea6425951d51487437f assets/dawd3/models/item/blue_patch_cable.json
|
||||
8c4b8147bfea2bdb8c1d348bc332a60d2cd82d68 assets/dawd3/models/item/phase.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
|
||||
d65fd7b21da2adf55a0b076103103da7e7bb453a assets/dawd3/models/block/speaker.json
|
||||
8aa966337109315240614d5257eb72f959eba5d8 assets/dawd3/models/item/orange_patch_cable.json
|
||||
12c4bfd825b2476955afcd3bb23c1f736ce68caa assets/dawd3/models/item/yellow_patch_cable.json
|
||||
aab1bce7ec4e7c7dccd3d33d4242de12b63a981d assets/dawd3/models/item/white_patch_cable.json
|
||||
6cdda36e539e23acf3db70d761338e88932a6ebb assets/dawd3/models/item/purple_patch_cable.json
|
||||
e3c6aacd49a6395f37047d3df31f91a18a411267 assets/dawd3/models/item/speaker.json
|
||||
32bb0e6e3bf75b9005602e8fb1042ad5d41286ad assets/dawd3/models/item/lime_patch_cable.json
|
||||
c5998d4ae85e7a16010e66b4b165ddd6b7f0f3ac assets/dawd3/models/block/phase.json
|
||||
956d8f117df95cf62c8cac375cff853df96840d6 assets/dawd3/models/item/pink_patch_cable.json
|
||||
3b1811bab3ba394ba03b67ac2efc72cb35316dc8 assets/dawd3/blockstates/sine_oscillator.json
|
||||
9c18a8292a0c9990cd23bebf5c6191c2114ccc6d assets/dawd3/models/item/black_patch_cable.json
|
||||
ff4e86bc6228d4ced17fc8df632bd9d3812a51d0 assets/dawd3/models/block/knob.json
|
||||
fb1d7e02f0cdd3807d828b0bec299c6533b9de57 assets/dawd3/models/block/sine_oscillator.json
|
||||
8688de3eadb6579cac89522683c394f18733e614 assets/dawd3/models/block/knob.json
|
||||
50023b9cff64844a4001e3e78d3f0ac828f2724a assets/dawd3/models/block/sine_oscillator.json
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
},
|
||||
"up": {
|
||||
"cullface": "up",
|
||||
"rotation": 180,
|
||||
"texture": "#top"
|
||||
},
|
||||
"west": {
|
||||
|
|
@ -39,30 +40,6 @@
|
|||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#port",
|
||||
"uv": [
|
||||
4.0,
|
||||
0.0,
|
||||
8.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
3.0,
|
||||
6.0,
|
||||
-0.01
|
||||
],
|
||||
"to": [
|
||||
7.0,
|
||||
10.0,
|
||||
0.01
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
|
|
@ -86,6 +63,30 @@
|
|||
10.0,
|
||||
0.01
|
||||
]
|
||||
},
|
||||
{
|
||||
"faces": {
|
||||
"north": {
|
||||
"cullface": "north",
|
||||
"texture": "#port",
|
||||
"uv": [
|
||||
4.0,
|
||||
0.0,
|
||||
8.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": [
|
||||
3.0,
|
||||
6.0,
|
||||
-0.01
|
||||
],
|
||||
"to": [
|
||||
7.0,
|
||||
10.0,
|
||||
0.01
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures": {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
},
|
||||
"up": {
|
||||
"cullface": "up",
|
||||
"rotation": 180,
|
||||
"texture": "#top"
|
||||
},
|
||||
"west": {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ object Audio {
|
|||
}
|
||||
|
||||
fun initializeClient() {
|
||||
// TODO: Could probably use the output stream's native pausing and unpausing capabilities here.
|
||||
D3ClientEvents.PAUSE.register { isPaused ->
|
||||
pauseAdapter.playing = !isPaused
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
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
|
||||
|
||||
class PhaseDevice : Device<NoControls> {
|
||||
companion object : DeviceDescriptor {
|
||||
override val id = Identifier(Mod.id, "phase")
|
||||
val frequencyCVPort = InputPortName(id, "frequency_cv")
|
||||
val outputPort = OutputPortName(id, "output")
|
||||
}
|
||||
|
||||
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, 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]
|
||||
phase += phaseStep
|
||||
phase %= 1.0f
|
||||
outputBuffer[i] = phase
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitInputPorts(visit: (InputPortName, InputPort) -> Unit) {
|
||||
visit(frequencyCVPort, frequencyCV)
|
||||
}
|
||||
|
||||
override fun visitOutputPorts(visit: (OutputPortName, OutputPort) -> Unit) {
|
||||
visit(outputPort, output)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
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
|
||||
|
||||
|
|
@ -13,34 +10,23 @@ private const val twoPi = 2.0f * kotlin.math.PI.toFloat()
|
|||
class SineOscillatorDevice : Device<NoControls> {
|
||||
companion object : DeviceDescriptor {
|
||||
override val id = Identifier(Mod.id, "sine_oscillator")
|
||||
val frequencyCVPort = InputPortName(id, "frequency_cv")
|
||||
val phasePort = InputPortName(id, "phase")
|
||||
val outputPort = OutputPortName(id, "output")
|
||||
}
|
||||
|
||||
private val frequencyCV = InputPort()
|
||||
private val phase = InputPort()
|
||||
private val output = OutputPort(bufferCount = 1)
|
||||
|
||||
private val frequencyBuffer = AudioBuffer()
|
||||
private var phase = 0.0f
|
||||
|
||||
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 phase = phase.getConnectedOutputBuffer(0, sampleCount)
|
||||
val outputBuffer = output.buffers[0].getOrReallocate(sampleCount)
|
||||
for (i in 0 until sampleCount) {
|
||||
val phaseStep = Audio.sampleRateFInv * frequencyBuffer[i]
|
||||
phase += phaseStep
|
||||
phase %= 1.0f
|
||||
outputBuffer[i] = sin(phase * twoPi)
|
||||
outputBuffer[i] = sin(phase[i] * twoPi)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitInputPorts(visit: (InputPortName, InputPort) -> Unit) {
|
||||
visit(frequencyCVPort, frequencyCV)
|
||||
visit(phasePort, phase)
|
||||
}
|
||||
|
||||
override fun visitOutputPorts(visit: (OutputPortName, OutputPort) -> Unit) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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.KnobBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.PhaseBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.SineOscillatorBlockDescriptor
|
||||
import net.liquidev.dawd3.block.devices.SpeakerBlockDescriptor
|
||||
import net.liquidev.dawd3.item.Items
|
||||
|
|
@ -56,12 +57,13 @@ object Blocks {
|
|||
Registry.BLOCK,
|
||||
Identifier(Mod.id, "patch_cable_plug"),
|
||||
Block(FabricBlockSettings.of(Material.METAL))
|
||||
)
|
||||
)!!
|
||||
|
||||
// Device blocks
|
||||
val speaker = registerDeviceBlock(SpeakerBlockDescriptor)
|
||||
val sineOscillator = registerDeviceBlock(SineOscillatorBlockDescriptor)
|
||||
val knob = registerDeviceBlock(KnobBlockDescriptor)
|
||||
val phase = registerDeviceBlock(PhaseBlockDescriptor)
|
||||
val sineOscillator = registerDeviceBlock(SineOscillatorBlockDescriptor)
|
||||
|
||||
fun initialize() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,18 +90,27 @@ class DeviceBlock(private val descriptor: AnyDeviceBlockDescriptor) :
|
|||
// 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.
|
||||
// TODO: Right-clicking the port should pop out its patch cable, not just remove it.
|
||||
|
||||
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) {
|
||||
// We have to do a little dance such that the server executes this code too and doesn't
|
||||
// try to perform the item action if the UI is opened.
|
||||
val adjacentDevices = Rack.collectAdjacentDevices(world, pos)
|
||||
val shouldOpenUI = adjacentDevices.any { blockPos ->
|
||||
val blockEntityAtPosition =
|
||||
world.getBlockEntity(blockPos) as? DeviceBlockEntity ?: return@any false
|
||||
blockEntityAtPosition.descriptor.ui != null
|
||||
}
|
||||
if (shouldOpenUI) {
|
||||
if (world is ClientWorld) {
|
||||
MinecraftClient.getInstance().setScreen(Rack(world, adjacentDevices))
|
||||
}
|
||||
return ActionResult.success(world.isClient)
|
||||
}
|
||||
}
|
||||
if (usedPortName != null && player.isSneaking) {
|
||||
return if (blockEntity.severConnectionsInPort(usedPortName)) {
|
||||
ActionResult.SUCCESS
|
||||
ActionResult.success(world.isClient)
|
||||
} else {
|
||||
ActionResult.PASS
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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.block.device.descriptor.FaceTextures
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.minecraft.block.AbstractBlock
|
||||
|
|
@ -40,6 +40,11 @@ interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out
|
|||
val logicalDevice: DeviceInstance
|
||||
}
|
||||
|
||||
fun openUI(controls: @UnsafeVariance Controls, x: Int, y: Int): Widget? = null
|
||||
interface UI<out Controls : ControlSet> {
|
||||
fun open(controls: @UnsafeVariance Controls, x: Int, y: Int): Widget
|
||||
}
|
||||
|
||||
val ui: UI<Controls>?
|
||||
get() = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
package net.liquidev.dawd3.block.device.descriptor
|
||||
|
||||
import net.minecraft.util.Identifier
|
||||
|
||||
interface FaceTextures {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
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.block.device.descriptor.FaceTextures
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Knob
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
|
|
@ -30,8 +30,7 @@ object KnobBlockDescriptor : DeviceBlockDescriptor<KnobBlockDescriptor.ClientSta
|
|||
override val faceTextures = FaceTextures.withFrontAndSide { id }
|
||||
|
||||
class ClientState(controls: ConstantDevice.Controls) : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice =
|
||||
DeviceInstance.create(ConstantDevice(), controls)
|
||||
override val logicalDevice = DeviceInstance.create(ConstantDevice(), controls)
|
||||
}
|
||||
|
||||
override fun initControls() = ConstantDevice.Controls()
|
||||
|
|
@ -39,17 +38,19 @@ object KnobBlockDescriptor : DeviceBlockDescriptor<KnobBlockDescriptor.ClientSta
|
|||
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
|
||||
override val ui = object : DeviceBlockDescriptor.UI<ConstantDevice.Controls> {
|
||||
override fun open(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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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.PhaseDevice
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockDescriptor
|
||||
import net.liquidev.dawd3.block.device.PhysicalPort
|
||||
import net.liquidev.dawd3.block.device.descriptor.FaceTextures
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.Vec2f
|
||||
|
||||
object PhaseBlockDescriptor : DeviceBlockDescriptor<PhaseBlockDescriptor.ClientState, NoControls> {
|
||||
override val id = Identifier(Mod.id, "phase")
|
||||
|
||||
override val portLayout = PhysicalPort.layout {
|
||||
port(
|
||||
PhaseDevice.frequencyCVPort,
|
||||
position = Vec2f(0.25f, 0.5f),
|
||||
side = PhysicalPort.Side.Front,
|
||||
)
|
||||
port(
|
||||
PhaseDevice.outputPort,
|
||||
position = Vec2f(0.75f, 0.5f),
|
||||
side = PhysicalPort.Side.Front
|
||||
)
|
||||
}
|
||||
override val faceTextures = FaceTextures.withFrontAndSide { id }
|
||||
|
||||
class ClientState(controls: NoControls) : DeviceBlockDescriptor.ClientState {
|
||||
override val logicalDevice = DeviceInstance.create(PhaseDevice(), controls)
|
||||
}
|
||||
|
||||
override fun initControls() = NoControls
|
||||
|
||||
override fun onClientLoad(controls: NoControls, world: ClientWorld) = ClientState(controls)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ object SineOscillatorBlockDescriptor : DeviceBlockDescriptor<SineOscillatorBlock
|
|||
|
||||
override val portLayout = PhysicalPort.layout {
|
||||
port(
|
||||
SineOscillatorDevice.frequencyCVPort,
|
||||
SineOscillatorDevice.phasePort,
|
||||
position = Vec2f(0.25f, 0.5f),
|
||||
side = PhysicalPort.Side.Front,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ object DeviceBlockModel {
|
|||
east = face(texture = "#right", cullface = "east"),
|
||||
south = face(texture = "#back", cullface = "south"),
|
||||
west = face(texture = "#left", cullface = "west"),
|
||||
up = face(texture = "#top", cullface = "up"),
|
||||
up = face(texture = "#top", cullface = "up", rotation = 180),
|
||||
down = face(texture = "#bottom", cullface = "down"),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
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
|
||||
|
|
@ -16,7 +14,6 @@ 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>,
|
||||
|
|
@ -30,7 +27,8 @@ class Rack(
|
|||
var windowX = 32
|
||||
ArrayList(shownDevices.mapNotNull { blockPosition ->
|
||||
val blockEntity = world.getBlockEntity(blockPosition) as DeviceBlockEntity
|
||||
blockEntity.descriptor.openUI(blockEntity.controls, windowX, 32)
|
||||
blockEntity.descriptor.ui
|
||||
?.open(blockEntity.controls, windowX, 32)
|
||||
?.let { widget ->
|
||||
windowX += widget.width + 8
|
||||
OpenWidget(blockPosition, widget)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@
|
|||
"item.dawd3.green_patch_cable": "Green Patch Cable",
|
||||
"item.dawd3.red_patch_cable": "Red Patch Cable",
|
||||
"item.dawd3.black_patch_cable": "Black Patch Cable",
|
||||
"block.dawd3.knob": "Knob",
|
||||
"block.dawd3.phase": "Phase",
|
||||
"block.dawd3.speaker": "Speaker",
|
||||
"block.dawd3.sine_oscillator": "Sine Oscillator",
|
||||
"block.dawd3.knob": "Knob",
|
||||
"screen.dawd3.rack.title": "Rack"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 298 B |
Reference in a new issue