mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 03:24:06 +09:00
export poi to file
This commit is contained in:
@@ -5,7 +5,6 @@ import com.badlogic.gdx.InputAdapter
|
|||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||||
import net.torvald.gdx.graphics.Cvec
|
|
||||||
import net.torvald.terrarum.*
|
import net.torvald.terrarum.*
|
||||||
import net.torvald.terrarum.App.printdbg
|
import net.torvald.terrarum.App.printdbg
|
||||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||||
@@ -32,6 +31,7 @@ import net.torvald.terrarum.weather.WeatherMixer
|
|||||||
import net.torvald.terrarum.ui.UINSMenu
|
import net.torvald.terrarum.ui.UINSMenu
|
||||||
import net.torvald.terrarum.worlddrawer.WorldCamera
|
import net.torvald.terrarum.worlddrawer.WorldCamera
|
||||||
import net.torvald.util.CircularArray
|
import net.torvald.util.CircularArray
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2018-07-06.
|
* Created by minjaesong on 2018-07-06.
|
||||||
@@ -81,9 +81,13 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
val uiPalette = UIBuildingMakerBlockChooser(this)
|
val uiPalette = UIBuildingMakerBlockChooser(this)
|
||||||
val uiPenMenu = UIBuildingMakerPenMenu(this)
|
val uiPenMenu = UIBuildingMakerPenMenu(this)
|
||||||
|
|
||||||
|
val uiGetPoiName = UIBuildingMakerGetFilename() // used for both import and export
|
||||||
|
|
||||||
val uiContainer = UIContainer()
|
val uiContainer = UIContainer()
|
||||||
|
|
||||||
|
val keyboardUsedByTextInput: Boolean
|
||||||
|
get() = uiGetPoiName.textInput.isEnabled
|
||||||
|
|
||||||
private val pensMustShowSelection = arrayOf(
|
private val pensMustShowSelection = arrayOf(
|
||||||
PENMODE_MARQUEE, PENMODE_MARQUEE_ERASE
|
PENMODE_MARQUEE, PENMODE_MARQUEE_ERASE
|
||||||
)
|
)
|
||||||
@@ -254,6 +258,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
uiContainer.add(notifier)
|
uiContainer.add(notifier)
|
||||||
uiContainer.add(uiPalette)
|
uiContainer.add(uiPalette)
|
||||||
uiContainer.add(uiPenMenu)
|
uiContainer.add(uiPenMenu)
|
||||||
|
uiContainer.add(uiGetPoiName)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -314,7 +319,11 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
|
|
||||||
WeatherMixer.update(delta, actorNowPlaying, gameWorld)
|
WeatherMixer.update(delta, actorNowPlaying, gameWorld)
|
||||||
blockPointingCursor.update(delta)
|
blockPointingCursor.update(delta)
|
||||||
actorNowPlaying?.update(delta)
|
|
||||||
|
if (!keyboardUsedByTextInput) {
|
||||||
|
actorNowPlaying?.update(delta)
|
||||||
|
}
|
||||||
|
|
||||||
var overwriteMouseOnUI = false
|
var overwriteMouseOnUI = false
|
||||||
uiContainer.forEach {
|
uiContainer.forEach {
|
||||||
it?.update(delta)
|
it?.update(delta)
|
||||||
@@ -323,7 +332,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseOnUI = (overwriteMouseOnUI || uiPenMenu.isVisible)
|
mouseOnUI = (overwriteMouseOnUI || uiPenMenu.isVisible || uiPalette.isVisible || uiGetPoiName.isVisible)
|
||||||
|
|
||||||
|
|
||||||
WorldCamera.update(world, actorNowPlaying)
|
WorldCamera.update(world, actorNowPlaying)
|
||||||
@@ -403,10 +412,37 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
// blockMarkings.dispose()
|
// blockMarkings.dispose()
|
||||||
uiPenMenu.dispose()
|
uiPenMenu.dispose()
|
||||||
|
uiGetPoiName.dispose()
|
||||||
musicGovernor.dispose()
|
musicGovernor.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inputStrobed(e: TerrarumKeyboardEvent) {
|
fun getPoiNameForExport(w: Int, h: Int, callback: (String) -> Unit) {
|
||||||
|
uiGetPoiName.let {
|
||||||
|
it.title = "Export"
|
||||||
|
it.labelDo = "Export"
|
||||||
|
it.text = listOf("WH: $w\u00D7$h", "Name of the POI:")
|
||||||
|
it.confirmCallback = callback
|
||||||
|
it.setPosition(
|
||||||
|
240,
|
||||||
|
32
|
||||||
|
)
|
||||||
|
it.reset()
|
||||||
|
it.setAsOpen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPoiNameForImport(callback: (String) -> Unit) {
|
||||||
|
uiGetPoiName.let {
|
||||||
|
it.title = "Import"
|
||||||
|
it.labelDo = "Import"
|
||||||
|
it.text = listOf("Name of the POI:")
|
||||||
|
it.confirmCallback = callback
|
||||||
|
it.setPosition(
|
||||||
|
240,
|
||||||
|
32
|
||||||
|
)
|
||||||
|
it.setAsOpen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makePenWork(x: Int, y: Int) {
|
private fun makePenWork(x: Int, y: Int) {
|
||||||
@@ -486,6 +522,12 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
fos.write(FILE_FOOTER)
|
fos.write(FILE_FOOTER)
|
||||||
fos.close()
|
fos.close()
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
override fun inputStrobed(e: TerrarumKeyboardEvent) {
|
||||||
|
uiContainer.forEach {
|
||||||
|
it?.inputStrobed(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BuildingMakerController(val screen: BuildingMaker) : InputAdapter() {
|
class BuildingMakerController(val screen: BuildingMaker) : InputAdapter() {
|
||||||
@@ -676,7 +718,7 @@ class YamlCommandToolMarqueeErase : YamlInvokable {
|
|||||||
class YamlCommandToolMarqueeClear : YamlInvokable {
|
class YamlCommandToolMarqueeClear : YamlInvokable {
|
||||||
override fun invoke(args: Array<Any>) {
|
override fun invoke(args: Array<Any>) {
|
||||||
(args[0] as BuildingMaker).selection.toList().forEach {
|
(args[0] as BuildingMaker).selection.toList().forEach {
|
||||||
val (x, y) = (it % 4294967296L).toInt() to (it / 4294967296).toInt()
|
val (x, y) = (it % 4294967296).toInt() to (it / 4294967296).toInt()
|
||||||
(args[0] as BuildingMaker).removeBlockMarker(x, y)
|
(args[0] as BuildingMaker).removeBlockMarker(x, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,9 +745,7 @@ class YamlCommandToolExportTest : YamlInvokable {
|
|||||||
var maxX = -1
|
var maxX = -1
|
||||||
var maxY = -1
|
var maxY = -1
|
||||||
ui.selection.forEach {
|
ui.selection.forEach {
|
||||||
val (x, y) = (it % 4294967296L).toInt() to (it / 4294967296).toInt()
|
val (x, y) = (it % 4294967296).toInt() to (it / 4294967296).toInt()
|
||||||
|
|
||||||
printdbg(this, "Selection: ($x,$y)")
|
|
||||||
|
|
||||||
if (x < minX) minX = x
|
if (x < minX) minX = x
|
||||||
if (y < minY) minY = y
|
if (y < minY) minY = y
|
||||||
@@ -715,48 +755,60 @@ class YamlCommandToolExportTest : YamlInvokable {
|
|||||||
marked.add(it)
|
marked.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
printdbg(this, "POI Area: ($minX,$minY)..($maxX,$maxY), WH=(${maxX - minX},${maxY - minY})")
|
ui.getPoiNameForExport(maxX - minX + 1, maxY - minY + 1) { name ->
|
||||||
|
// prepare POI
|
||||||
|
val poi = PointOfInterest(
|
||||||
|
name,
|
||||||
|
maxX - minX + 1,
|
||||||
|
maxY - minY + 1,
|
||||||
|
ui.world.tileNumberToNameMap,
|
||||||
|
ui.world.tileNameToNumberMap
|
||||||
|
)
|
||||||
|
val layer = POILayer(name)
|
||||||
|
val terr = BlockLayerI16(poi.w, poi.h)
|
||||||
|
val wall = BlockLayerI16(poi.w, poi.h)
|
||||||
|
layer.blockLayer = arrayListOf(terr, wall)
|
||||||
|
poi.layers.add(layer)
|
||||||
|
|
||||||
var name = "test"
|
for (x in minX..maxX) {
|
||||||
|
for (y in minY..maxY) {
|
||||||
// prepare POI
|
if (isMarked(x, y)) {
|
||||||
val poi = PointOfInterest(name, maxX - minX + 1, maxY - minY + 1, ui.world.tileNumberToNameMap, ui.world.tileNameToNumberMap)
|
terr.unsafeSetTile(x - minX, y - minY, ui.world.getTileFromTerrainRaw(x, y))
|
||||||
val layer = POILayer(name)
|
wall.unsafeSetTile(x - minX, y - minY, ui.world.getTileFromWallRaw(x, y))
|
||||||
val terr = BlockLayerI16(poi.w, poi.h)
|
}
|
||||||
val wall = BlockLayerI16(poi.w, poi.h)
|
else {
|
||||||
layer.blockLayer = arrayListOf(terr, wall)
|
terr.unsafeSetTile(x - minX, y - minY, -1)
|
||||||
poi.layers.add(layer)
|
wall.unsafeSetTile(x - minX, y - minY, -1)
|
||||||
|
}
|
||||||
for (x in minX..maxX) {
|
|
||||||
for (y in minY..maxY) {
|
|
||||||
if (isMarked(x, y)) {
|
|
||||||
terr.unsafeSetTile(x - minX, y - minY, ui.world.getTileFromTerrainRaw(x, y))
|
|
||||||
wall.unsafeSetTile(x - minX, y - minY, ui.world.getTileFromWallRaw(x, y))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
terr.unsafeSetTile(x - minX, y - minY, -1)
|
|
||||||
wall.unsafeSetTile(x - minX, y - minY, -1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// process POI for export
|
||||||
|
val json = Common.jsoner
|
||||||
|
val jsonStr = json.toJson(poi)
|
||||||
|
|
||||||
|
|
||||||
|
val dir = App.defaultDir + "/Exports/"
|
||||||
|
val dirAsFile = File(dir)
|
||||||
|
if (!dirAsFile.exists()) {
|
||||||
|
dirAsFile.mkdir()
|
||||||
|
}
|
||||||
|
|
||||||
|
File(dirAsFile, "$name.poi").writeText(jsonStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// process POI for export
|
|
||||||
val json = Common.jsoner
|
|
||||||
val jsonStr = json.toJson(poi)
|
|
||||||
printdbg(this, "Json:\n$jsonStr")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Point2i.toAddr() = toAddr(this.x, this.y)
|
private fun Point2i.toAddr() = toAddr(this.x, this.y)
|
||||||
private fun toAddr(x: Int, y: Int) = (x.toLong().shl(32) or y.toLong().and(0xFFFFFFFFL))
|
private fun toAddr(x: Int, y: Int) = (y.toLong().shl(32) or x.toLong().and(0xFFFFFFFFL))
|
||||||
|
|
||||||
class YamlCommandClearSelection : YamlInvokable {
|
class YamlCommandClearSelection : YamlInvokable {
|
||||||
override fun invoke(args: Array<Any>) {
|
override fun invoke(args: Array<Any>) {
|
||||||
val ui = (args[0] as BuildingMaker)
|
val ui = (args[0] as BuildingMaker)
|
||||||
try {
|
try {
|
||||||
(ui.selection.clone() as ArrayList<Point2i>).forEach { (x, y) ->
|
(ui.selection.clone() as ArrayList<Long>).forEach { i ->
|
||||||
ui.removeBlockMarker(x, y)
|
ui.removeBlockMarker((i % 4294967296).toInt(), (i / 4294967296).toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e: NullPointerException) {}
|
catch (e: NullPointerException) {}
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package net.torvald.terrarum.modulebasegame
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.OrthographicCamera
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import net.torvald.terrarum.*
|
||||||
|
import net.torvald.terrarum.ui.*
|
||||||
|
import net.torvald.terrarum.ui.UIItemTextButtonList.Companion.DEFAULT_BACKGROUNDCOL
|
||||||
|
import net.torvald.terrarum.ui.UINSMenu.Companion.LINE_HEIGHT
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class UIBuildingMakerGetFilename : UICanvas() {
|
||||||
|
|
||||||
|
var confirmCallback: (String) -> Unit = {}
|
||||||
|
var title = "Export"
|
||||||
|
var text = listOf("")
|
||||||
|
|
||||||
|
var labelDo = "OK"
|
||||||
|
var labelDont = "Cancel"
|
||||||
|
|
||||||
|
override var width = 280
|
||||||
|
override var height = 160
|
||||||
|
|
||||||
|
private val textWidth = width - LINE_HEIGHT
|
||||||
|
private val buttonWidth = 101
|
||||||
|
private val buttonGap = (width - buttonWidth*2) / 3
|
||||||
|
|
||||||
|
override var openCloseTime = OPENCLOSE_GENERIC
|
||||||
|
|
||||||
|
val textInput = UIItemTextLineInput(
|
||||||
|
this,
|
||||||
|
(width - textWidth) / 2,
|
||||||
|
LINE_HEIGHT * (text.size + 2),
|
||||||
|
textWidth,
|
||||||
|
{ "The Yucky Panopticon" },
|
||||||
|
InputLenCap(250, InputLenCap.CharLenUnit.UTF8_BYTES)
|
||||||
|
)
|
||||||
|
|
||||||
|
val buttonOk = UIItemTextButton(
|
||||||
|
this,
|
||||||
|
{ labelDo },
|
||||||
|
buttonGap,
|
||||||
|
height - LINE_HEIGHT - LINE_HEIGHT/2,
|
||||||
|
buttonWidth
|
||||||
|
).also {
|
||||||
|
it.clickOnceListener = { _, _ ->
|
||||||
|
|
||||||
|
textInput.getTextOrPlaceholder().let { name ->
|
||||||
|
if (name.isNotBlank())
|
||||||
|
confirmCallback(name.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setAsClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val buttonCancel = UIItemTextButton(
|
||||||
|
this,
|
||||||
|
{ labelDont },
|
||||||
|
width - buttonWidth - buttonGap,
|
||||||
|
height - LINE_HEIGHT - LINE_HEIGHT/2,
|
||||||
|
buttonWidth
|
||||||
|
).also {
|
||||||
|
it.clickOnceListener = { _, _ ->
|
||||||
|
reset()
|
||||||
|
this.setAsClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
textInput.clearText()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
addUIitem(textInput)
|
||||||
|
addUIitem(buttonOk)
|
||||||
|
addUIitem(buttonCancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateUI(delta: Float) {
|
||||||
|
uiItems.forEach { it.update(delta) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun renderUI(batch: SpriteBatch, camera: OrthographicCamera) {
|
||||||
|
blendNormalStraightAlpha(batch)
|
||||||
|
|
||||||
|
// draw border
|
||||||
|
batch.color = Toolkit.Theme.COL_INACTIVE
|
||||||
|
Toolkit.drawBoxBorder(batch, -1, -1 - LINE_HEIGHT, width + 2, height + LINE_HEIGHT + 2)
|
||||||
|
|
||||||
|
// draw title bar
|
||||||
|
batch.color = UINSMenu.DEFAULT_TITLEBACKCOL
|
||||||
|
Toolkit.fillArea(batch, 0, 0 - LINE_HEIGHT, width, LINE_HEIGHT)
|
||||||
|
|
||||||
|
batch.color = UINSMenu.DEFAULT_TITLETEXTCOL
|
||||||
|
App.fontGame.draw(batch, title, UINSMenu.TEXT_OFFSETX + 0, UINSMenu.TEXT_OFFSETY + 0 - LINE_HEIGHT)
|
||||||
|
|
||||||
|
// draw the back
|
||||||
|
batch.color = DEFAULT_BACKGROUNDCOL
|
||||||
|
Toolkit.fillArea(batch, 0, 0, width, height)
|
||||||
|
|
||||||
|
|
||||||
|
// draw the list
|
||||||
|
batch.color = Color.WHITE
|
||||||
|
val textWidth: Int = text.maxOf { App.fontGame.getWidth(it) }
|
||||||
|
|
||||||
|
text.forEachIndexed { index, str ->
|
||||||
|
App.fontGame.draw(batch, str, 0 + LINE_HEIGHT / 2, 0 + LINE_HEIGHT / 2 + LINE_HEIGHT * index)
|
||||||
|
}
|
||||||
|
|
||||||
|
uiItems.forEach { it.render(batch, camera) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var dragOriginX = 0 // relative mousepos
|
||||||
|
private var dragOriginY = 0 // relative mousepos
|
||||||
|
private var dragForReal = false
|
||||||
|
|
||||||
|
override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean {
|
||||||
|
if (mouseInScreen(screenX, screenY)) {
|
||||||
|
if (dragForReal) {
|
||||||
|
handler.setPosition(
|
||||||
|
(screenX / App.scr.magn - dragOriginX).roundToInt(),
|
||||||
|
(screenY / App.scr.magn - dragOriginY).roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uiItems.forEach { it.touchDragged(screenX, screenY, pointer) }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mouseOnTitleBar() =
|
||||||
|
relativeMouseX in 0 until width && relativeMouseY in -LINE_HEIGHT until 0
|
||||||
|
|
||||||
|
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
|
||||||
|
if (mouseOnTitleBar()) {
|
||||||
|
dragOriginX = relativeMouseX
|
||||||
|
dragOriginY = relativeMouseY
|
||||||
|
dragForReal = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dragForReal = false
|
||||||
|
}
|
||||||
|
|
||||||
|
uiItems.forEach { it.touchDown(screenX, screenY, pointer, button) }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
uiItems.forEach { it.tryDispose() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,9 +58,9 @@ class PointOfInterest(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
printdbg(this, "unique tiles: ${tileSymbolToItemId.size}")
|
// printdbg(this, "unique tiles: ${tileSymbolToItemId.size}")
|
||||||
printdbg(this, "tileSymbolToItemId=$tileSymbolToItemId")
|
// printdbg(this, "tileSymbolToItemId=$tileSymbolToItemId")
|
||||||
printdbg(this, "itemIDtoTileSym=$itemIDtoTileSym")
|
// printdbg(this, "itemIDtoTileSym=$itemIDtoTileSym")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class UIItemTextButtonList(
|
|||||||
|
|
||||||
batch.color = backgroundCol
|
batch.color = backgroundCol
|
||||||
blendNormalStraightAlpha(batch)
|
blendNormalStraightAlpha(batch)
|
||||||
Toolkit.fillArea(batch, posX.toFloat(), posY.toFloat(), width.toFloat(), height.toFloat())
|
Toolkit.fillArea(batch, posX, posY, width, height)
|
||||||
|
|
||||||
|
|
||||||
buttons.forEach { it.render(batch, camera) }
|
buttons.forEach { it.render(batch, camera) }
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ class UINSMenu(
|
|||||||
companion object {
|
companion object {
|
||||||
val DEFAULT_TITLEBACKCOL = Color(0f, 0f, 0f, .77f)
|
val DEFAULT_TITLEBACKCOL = Color(0f, 0f, 0f, .77f)
|
||||||
val DEFAULT_TITLETEXTCOL = Color.WHITE
|
val DEFAULT_TITLETEXTCOL = Color.WHITE
|
||||||
|
val LINE_HEIGHT = 24
|
||||||
|
val TEXT_OFFSETX = 3f
|
||||||
|
val TEXT_OFFSETY = (LINE_HEIGHT - App.fontGame.lineHeight) / 2f
|
||||||
}
|
}
|
||||||
|
|
||||||
override var openCloseTime: Second = 0f
|
override var openCloseTime: Second = 0f
|
||||||
val LINE_HEIGHT = 24
|
|
||||||
val TEXT_OFFSETX = 3f
|
|
||||||
val TEXT_OFFSETY = (LINE_HEIGHT - App.fontGame.lineHeight) / 2f
|
|
||||||
val CHILD_ARROW = "${0x2023.toChar()}"
|
val CHILD_ARROW = "${0x2023.toChar()}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user