Add edit backpacks button to /firm storage
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import io.github.notenoughupdates.moulconfig.common.IMinecraft
|
||||
import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
|
||||
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener
|
||||
import io.github.notenoughupdates.moulconfig.gui.GuiComponent
|
||||
import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper
|
||||
import io.github.notenoughupdates.moulconfig.gui.GuiContext
|
||||
import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
|
||||
import io.github.notenoughupdates.moulconfig.gui.MouseEvent
|
||||
import io.github.notenoughupdates.moulconfig.observer.GetSetter
|
||||
import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
|
||||
import io.github.notenoughupdates.moulconfig.xml.ChildCount
|
||||
import io.github.notenoughupdates.moulconfig.xml.XMLContext
|
||||
import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader
|
||||
@@ -19,6 +22,7 @@ import me.shedaniel.math.Color
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import moe.nea.firmament.gui.BarComponent
|
||||
import moe.nea.firmament.gui.FirmButtonComponent
|
||||
@@ -26,205 +30,254 @@ import moe.nea.firmament.gui.FirmHoverComponent
|
||||
import moe.nea.firmament.gui.FixedComponent
|
||||
import moe.nea.firmament.gui.ImageComponent
|
||||
import moe.nea.firmament.gui.TickComponent
|
||||
import moe.nea.firmament.util.render.isUntranslatedGuiDrawContext
|
||||
|
||||
object MoulConfigUtils {
|
||||
val firmUrl = "http://firmament.nea.moe/moulconfig"
|
||||
val universe = XMLUniverse.getDefaultUniverse().also { uni ->
|
||||
uni.registerMapper(java.awt.Color::class.java) {
|
||||
if (it.startsWith("#")) {
|
||||
val hexString = it.substring(1)
|
||||
val hex = hexString.toInt(16)
|
||||
if (hexString.length == 6) {
|
||||
return@registerMapper java.awt.Color(hex)
|
||||
}
|
||||
if (hexString.length == 8) {
|
||||
return@registerMapper java.awt.Color(hex, true)
|
||||
}
|
||||
error("Hexcolor $it needs to be exactly 6 or 8 hex digits long")
|
||||
}
|
||||
return@registerMapper java.awt.Color(it.toInt(), true)
|
||||
}
|
||||
uni.registerMapper(Color::class.java) {
|
||||
val color = uni.mapXMLObject(it, java.awt.Color::class.java)
|
||||
Color.ofRGBA(color.red, color.green, color.blue, color.alpha)
|
||||
}
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> {
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Bar")
|
||||
}
|
||||
val firmUrl = "http://firmament.nea.moe/moulconfig"
|
||||
val universe = XMLUniverse.getDefaultUniverse().also { uni ->
|
||||
uni.registerMapper(java.awt.Color::class.java) {
|
||||
if (it.startsWith("#")) {
|
||||
val hexString = it.substring(1)
|
||||
val hex = hexString.toInt(16)
|
||||
if (hexString.length == 6) {
|
||||
return@registerMapper java.awt.Color(hex)
|
||||
}
|
||||
if (hexString.length == 8) {
|
||||
return@registerMapper java.awt.Color(hex, true)
|
||||
}
|
||||
error("Hexcolor $it needs to be exactly 6 or 8 hex digits long")
|
||||
}
|
||||
return@registerMapper java.awt.Color(it.toInt(), true)
|
||||
}
|
||||
uni.registerMapper(Color::class.java) {
|
||||
val color = uni.mapXMLObject(it, java.awt.Color::class.java)
|
||||
Color.ofRGBA(color.red, color.green, color.blue, color.alpha)
|
||||
}
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> {
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Bar")
|
||||
}
|
||||
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): BarComponent {
|
||||
return BarComponent(
|
||||
context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(),
|
||||
)
|
||||
}
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): BarComponent {
|
||||
return BarComponent(
|
||||
context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent {
|
||||
return FirmHoverComponent(
|
||||
context.getChildFragment(element),
|
||||
context.getPropertyFromAttribute(element, QName("lines"), List::class.java) as Supplier<List<String>>,
|
||||
context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds),
|
||||
)
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent {
|
||||
return FirmHoverComponent(
|
||||
context.getChildFragment(element),
|
||||
context.getPropertyFromAttribute(element,
|
||||
QName("lines"),
|
||||
List::class.java) as Supplier<List<String>>,
|
||||
context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Hover")
|
||||
}
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Hover")
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf(
|
||||
"lines" to true,
|
||||
"delay" to false,
|
||||
)
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf(
|
||||
"lines" to true,
|
||||
"delay" to false,
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> {
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Button")
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> {
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Button")
|
||||
}
|
||||
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent {
|
||||
return FirmButtonComponent(
|
||||
context.getChildFragment(element),
|
||||
context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java)
|
||||
?: GetSetter.constant(true),
|
||||
context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false),
|
||||
context.getMethodFromAttribute(element, QName("onClick")),
|
||||
)
|
||||
}
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent {
|
||||
return FirmButtonComponent(
|
||||
context.getChildFragment(element),
|
||||
context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java)
|
||||
?: GetSetter.constant(true),
|
||||
context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false),
|
||||
context.getMethodFromAttribute(element, QName("onClick")),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("onClick" to true, "enabled" to false, "noBackground" to false)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent {
|
||||
return ImageComponent(
|
||||
context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f),
|
||||
context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f),
|
||||
context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f),
|
||||
context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f),
|
||||
)
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("onClick" to true, "enabled" to false, "noBackground" to false)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent {
|
||||
return ImageComponent(
|
||||
context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(),
|
||||
context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!,
|
||||
context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f),
|
||||
context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f),
|
||||
context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f),
|
||||
context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Image")
|
||||
}
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Image")
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf(
|
||||
"width" to true, "height" to true,
|
||||
"resource" to true,
|
||||
"u1" to false,
|
||||
"u2" to false,
|
||||
"v1" to false,
|
||||
"v2" to false,
|
||||
)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): TickComponent {
|
||||
return TickComponent(context.getMethodFromAttribute(element, QName("tick")))
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf(
|
||||
"width" to true, "height" to true,
|
||||
"resource" to true,
|
||||
"u1" to false,
|
||||
"u2" to false,
|
||||
"v1" to false,
|
||||
"v2" to false,
|
||||
)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): TickComponent {
|
||||
return TickComponent(context.getMethodFromAttribute(element, QName("tick")))
|
||||
}
|
||||
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Tick")
|
||||
}
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Tick")
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.NONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("tick" to true)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent {
|
||||
return FixedComponent(
|
||||
context.getPropertyFromAttribute(element, QName("width"), Int::class.java)
|
||||
?: error("Requires width specified"),
|
||||
context.getPropertyFromAttribute(element, QName("height"), Int::class.java)
|
||||
?: error("Requires height specified"),
|
||||
context.getChildFragment(element)
|
||||
)
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("tick" to true)
|
||||
}
|
||||
})
|
||||
uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> {
|
||||
override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent {
|
||||
return FixedComponent(
|
||||
context.getPropertyFromAttribute(element, QName("width"), Int::class.java)
|
||||
?: error("Requires width specified"),
|
||||
context.getPropertyFromAttribute(element, QName("height"), Int::class.java)
|
||||
?: error("Requires height specified"),
|
||||
context.getChildFragment(element)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Fixed")
|
||||
}
|
||||
override fun getName(): QName {
|
||||
return QName(firmUrl, "Fixed")
|
||||
}
|
||||
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
override fun getChildCount(): ChildCount {
|
||||
return ChildCount.ONE
|
||||
}
|
||||
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("width" to true, "height" to true)
|
||||
}
|
||||
})
|
||||
}
|
||||
override fun getAttributeNames(): Map<String, Boolean> {
|
||||
return mapOf("width" to true, "height" to true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun generateXSD(
|
||||
file: File,
|
||||
namespace: String
|
||||
) {
|
||||
val generator = XSDGenerator(universe, namespace)
|
||||
generator.writeAll()
|
||||
generator.dumpToFile(file)
|
||||
}
|
||||
fun generateXSD(
|
||||
file: File,
|
||||
namespace: String
|
||||
) {
|
||||
val generator = XSDGenerator(universe, namespace)
|
||||
generator.writeAll()
|
||||
generator.dumpToFile(file)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<out String>) {
|
||||
generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS)
|
||||
generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl)
|
||||
File("wrapper.xsd").writeText("""
|
||||
@JvmStatic
|
||||
fun main(args: Array<out String>) {
|
||||
generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS)
|
||||
generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl)
|
||||
File("wrapper.xsd").writeText("""
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/>
|
||||
<xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/>
|
||||
</xs:schema>
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
||||
fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen {
|
||||
return object : GuiComponentWrapper(loadGui(name, bindTo)) {
|
||||
override fun close() {
|
||||
if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
|
||||
client!!.setScreen(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen {
|
||||
return object : GuiComponentWrapper(loadGui(name, bindTo)) {
|
||||
override fun close() {
|
||||
if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
|
||||
client!!.setScreen(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadGui(name: String, bindTo: Any): GuiContext {
|
||||
return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml")))
|
||||
}
|
||||
// TODO: move this utility into moulconfig (also rework guicontext into an interface so i can make this mesh better into vanilla)
|
||||
fun GuiContext.adopt(element: GuiComponent) = element.foldRecursive(Unit, { comp, unit -> comp.context = this })
|
||||
|
||||
fun clickMCComponentInPlace(
|
||||
component: GuiComponent,
|
||||
x: Int,
|
||||
y: Int,
|
||||
w: Int,
|
||||
h: Int,
|
||||
mouseX: Int, mouseY: Int,
|
||||
mouseEvent: MouseEvent
|
||||
): Boolean {
|
||||
val immContext = createInPlaceFullContext(null, mouseX, mouseY)
|
||||
return component.mouseEvent(mouseEvent, immContext.translated(x, y, w, h))
|
||||
}
|
||||
|
||||
fun createInPlaceFullContext(drawContext: DrawContext?, mouseX: Int, mouseY: Int): GuiImmediateContext {
|
||||
assert(drawContext?.isUntranslatedGuiDrawContext() != false)
|
||||
val context = drawContext?.let(::ModernRenderContext)
|
||||
?: IMinecraft.instance.provideTopLevelRenderContext()
|
||||
val immContext = GuiImmediateContext(context,
|
||||
0, 0, 0, 0,
|
||||
mouseX, mouseY,
|
||||
mouseX, mouseY,
|
||||
mouseX.toFloat(),
|
||||
mouseY.toFloat())
|
||||
return immContext
|
||||
}
|
||||
|
||||
fun DrawContext.drawMCComponentInPlace(
|
||||
component: GuiComponent,
|
||||
x: Int,
|
||||
y: Int,
|
||||
w: Int,
|
||||
h: Int,
|
||||
mouseX: Int,
|
||||
mouseY: Int
|
||||
) {
|
||||
val immContext = createInPlaceFullContext(this, mouseX, mouseY)
|
||||
matrices.push()
|
||||
matrices.translate(x.toFloat(), y.toFloat(), 0F)
|
||||
component.render(immContext.translated(x, y, w, h))
|
||||
matrices.pop()
|
||||
}
|
||||
|
||||
|
||||
fun loadGui(name: String, bindTo: Any): GuiContext {
|
||||
return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
|
||||
@file:OptIn(ExperimentalContracts::class)
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Less aggressive version of `require(obj != null)`, which fails in devenv but continues at runtime.
|
||||
*/
|
||||
inline fun <T : Any> assertNotNullOr(obj: T?, message: String? = null, block: () -> T): T {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
if (message == null)
|
||||
assert(obj != null)
|
||||
else
|
||||
@@ -18,6 +25,9 @@ inline fun <T : Any> assertNotNullOr(obj: T?, message: String? = null, block: ()
|
||||
* Less aggressive version of `require(condition)`, which fails in devenv but continues at runtime.
|
||||
*/
|
||||
inline fun assertTrueOr(condition: Boolean, block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
assert(condition)
|
||||
if (!condition) block()
|
||||
}
|
||||
|
||||
8
src/main/kotlin/util/render/DrawContextExt.kt
Normal file
8
src/main/kotlin/util/render/DrawContextExt.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package moe.nea.firmament.util.render
|
||||
|
||||
import org.joml.Matrix4f
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
|
||||
fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
|
||||
return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.util.UUID
|
||||
|
||||
fun parseDashlessUUID(dashlessUuid: String): UUID {
|
||||
val most = BigInteger(dashlessUuid.substring(0, 16), 16)
|
||||
val least = BigInteger(dashlessUuid.substring(16, 32), 16)
|
||||
return UUID(most.toLong(), least.toLong())
|
||||
val most = BigInteger(dashlessUuid.substring(0, 16), 16)
|
||||
val least = BigInteger(dashlessUuid.substring(16, 32), 16)
|
||||
return UUID(most.toLong(), least.toLong())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user