New item viewer screen, that just does absolutely nothing
This commit is contained in:
parent
a401e7cf99
commit
4bb8f999d1
@ -8,6 +8,7 @@ import net.minecraftforge.network.PacketDistributor;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
import ru.dbotthepony.mc.otm.OverdriveThatMatters;
|
||||
import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket;
|
||||
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings;
|
||||
import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket;
|
||||
import ru.dbotthepony.mc.otm.item.weapon.WeaponScopePacket;
|
||||
import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket;
|
||||
@ -304,5 +305,14 @@ public class MatteryNetworking {
|
||||
ItemFilterSlotPacket::play
|
||||
// Optional.of(NetworkDirection.)
|
||||
);
|
||||
|
||||
CHANNEL.registerMessage(
|
||||
next_network_id++,
|
||||
ItemMonitorPlayerSettings.class,
|
||||
ItemMonitorPlayerSettings::write,
|
||||
ItemMonitorPlayerSettings.Companion::read,
|
||||
ItemMonitorPlayerSettings::play
|
||||
// Optional.of(NetworkDirection.)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -169,3 +169,20 @@ val ItemStack.tagNotNull: CompoundTag get() = orCreateTag
|
||||
inline var Entity.position: Vec3
|
||||
get() = position()
|
||||
set(value) { setPos(value) }
|
||||
|
||||
inline val <reified T : Enum<T>> T.next: T get() {
|
||||
val values = enumValues<T>()
|
||||
val next = (ordinal + 1) % values.size
|
||||
return values[next]
|
||||
}
|
||||
|
||||
inline val <reified T : Enum<T>> T.prev: T get() {
|
||||
val values = enumValues<T>()
|
||||
var next = ordinal - 1
|
||||
|
||||
if (next < 0) {
|
||||
next = values.size - 1
|
||||
}
|
||||
|
||||
return values[next]
|
||||
}
|
||||
|
@ -2,23 +2,127 @@ package ru.dbotthepony.mc.otm.block.entity.storage
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.StringTag
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.TranslatableComponent
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraftforge.common.capabilities.Capability
|
||||
import net.minecraftforge.common.util.INBTSerializable
|
||||
import net.minecraftforge.common.util.LazyOptional
|
||||
import net.minecraftforge.network.NetworkEvent
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode
|
||||
import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph
|
||||
import ru.dbotthepony.mc.otm.ifHas
|
||||
import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
import ru.dbotthepony.mc.otm.set
|
||||
import java.util.UUID
|
||||
import java.util.function.Supplier
|
||||
|
||||
class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag> {
|
||||
enum class MatchMode {
|
||||
FUZZY,
|
||||
EXACT
|
||||
}
|
||||
|
||||
enum class Target {
|
||||
SYSTEM,
|
||||
INVENTORY
|
||||
}
|
||||
|
||||
enum class Amount {
|
||||
ONE,
|
||||
STACK,
|
||||
FULL
|
||||
}
|
||||
|
||||
var ingredientMatchingMode: MatchMode = MatchMode.FUZZY
|
||||
var ingredientPriority = Target.SYSTEM
|
||||
var resultTarget = Target.INVENTORY
|
||||
var craftingAmount = Amount.STACK
|
||||
|
||||
override fun serializeNBT(): CompoundTag {
|
||||
return CompoundTag().also {
|
||||
it["ingredientMatchingMode"] = ingredientMatchingMode.name
|
||||
it["ingredientPriority"] = ingredientPriority.name
|
||||
it["resultTarget"] = resultTarget.name
|
||||
it["craftingAmount"] = craftingAmount.name
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(nbt: CompoundTag) {
|
||||
nbt.ifHas("ingredientMatchingMode", StringTag::class.java) { ingredientMatchingMode = MatchMode.valueOf(it.asString) }
|
||||
nbt.ifHas("ingredientPriority", StringTag::class.java) { ingredientPriority = Target.valueOf(it.asString) }
|
||||
nbt.ifHas("resultTarget", StringTag::class.java) { resultTarget = Target.valueOf(it.asString) }
|
||||
nbt.ifHas("craftingAmount", StringTag::class.java) { craftingAmount = Amount.valueOf(it.asString) }
|
||||
}
|
||||
|
||||
fun read(buff: FriendlyByteBuf) {
|
||||
ingredientMatchingMode = buff.readEnum(MatchMode::class.java)
|
||||
ingredientPriority = buff.readEnum(Target::class.java)
|
||||
resultTarget = buff.readEnum(Target::class.java)
|
||||
craftingAmount = buff.readEnum(Amount::class.java)
|
||||
}
|
||||
|
||||
fun read(other: ItemMonitorPlayerSettings) {
|
||||
ingredientMatchingMode = other.ingredientMatchingMode
|
||||
ingredientPriority = other.ingredientPriority
|
||||
resultTarget = other.resultTarget
|
||||
craftingAmount = other.craftingAmount
|
||||
}
|
||||
|
||||
fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeEnum(ingredientMatchingMode)
|
||||
buff.writeEnum(ingredientPriority)
|
||||
buff.writeEnum(resultTarget)
|
||||
buff.writeEnum(craftingAmount)
|
||||
}
|
||||
|
||||
private fun playClient() {
|
||||
val ply = minecraft.player ?: throw IllegalStateException("Player is missing")
|
||||
val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings but container is missing or not of right type ({})!", ply.containerMenu)
|
||||
|
||||
container.settings.read(this)
|
||||
}
|
||||
|
||||
private fun playServer(ply: ServerPlayer) {
|
||||
val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings from {} but container is missing or not of right type ({})!", ply, ply.containerMenu)
|
||||
container.settings.read(this)
|
||||
(container.tile as ItemMonitorBlockEntity).setChangedLight()
|
||||
}
|
||||
|
||||
fun play(context: Supplier<NetworkEvent.Context>) {
|
||||
context.get().packetHandled = true
|
||||
val ply = context.get().sender
|
||||
|
||||
if (ply != null) {
|
||||
playServer(ply)
|
||||
} else {
|
||||
playClient()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf): ItemMonitorPlayerSettings {
|
||||
return ItemMonitorPlayerSettings().also { it.read(buff) }
|
||||
}
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
||||
class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
|
||||
MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, p_155229_, p_155230_) {
|
||||
@ -26,6 +130,37 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
|
||||
override val energy = WorkerEnergyStorage(this)
|
||||
val cell = BasicStorageGraphNode(energy)
|
||||
|
||||
val settings = HashMap<UUID, ItemMonitorPlayerSettings>()
|
||||
|
||||
override fun saveAdditional(nbt: CompoundTag) {
|
||||
super.saveAdditional(nbt)
|
||||
|
||||
nbt.put("player_settings", CompoundTag().also {
|
||||
for ((key, value) in this.settings) {
|
||||
it[key.toString()] = value.serializeNBT()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun load(nbt: CompoundTag) {
|
||||
super.load(nbt)
|
||||
this.settings.clear()
|
||||
|
||||
val settings = nbt.getCompound("player_settings")
|
||||
|
||||
for (key in settings.allKeys) {
|
||||
val uuid = UUID.fromString(key)
|
||||
val deserialized = ItemMonitorPlayerSettings()
|
||||
deserialized.deserializeNBT(settings.getCompound(key))
|
||||
|
||||
check(this.settings.put(uuid, deserialized) == null) { "Duplicate UUID??? $uuid" }
|
||||
}
|
||||
}
|
||||
|
||||
fun getSettings(ply: ServerPlayer): ItemMonitorPlayerSettings {
|
||||
return settings.computeIfAbsent(ply.uuid) { ItemMonitorPlayerSettings() }
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
batteryChargeLoop()
|
||||
cell.tickEnergyDemanding()
|
||||
|
@ -5,20 +5,21 @@ import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
|
||||
import ru.dbotthepony.mc.otm.client.render.RenderHelper
|
||||
import ru.dbotthepony.mc.otm.client.render.SkinGrid
|
||||
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.*
|
||||
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
|
||||
import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu
|
||||
import ru.dbotthepony.mc.otm.next
|
||||
import ru.dbotthepony.mc.otm.prev
|
||||
|
||||
class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Component) :
|
||||
MatteryScreen<ItemMonitorMenu>(menu, inventory, title) {
|
||||
|
||||
override fun makeMainFrame(): FramePanel {
|
||||
val frame = object : FramePanel(this@ItemMonitorScreen, null, 0f, 0f, FRAME_WIDTH, FRAME_HEIGHT, getTitle()) {
|
||||
override fun innerRender(stack: PoseStack, mouse_x: Float, mouse_y: Float, flag: Float) {
|
||||
super.innerRender(stack, mouse_x, mouse_y, flag)
|
||||
// ProgressGaugePanel.GAUGE_BACKGROUND.render(stack, 28f + 3 * 18f + 6f, (ITEM_GRID_HEIGHT + 1) * 18f + 16f + 6f)
|
||||
}
|
||||
}
|
||||
val frame = FramePanel(this@ItemMonitorScreen, null, 0f, 0f, FRAME_WIDTH, FRAME_HEIGHT, getTitle())
|
||||
|
||||
val topPanel = EditablePanel(this, frame)
|
||||
topPanel.height = ITEM_GRID_HEIGHT * 18f
|
||||
@ -82,8 +83,32 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
|
||||
|
||||
val arrowLine = EditablePanel(this, arrowAndButtons, y = 8f, height = 8f, width = arrowAndButtons.width)
|
||||
|
||||
val autoRefillMatchMode = SmallSquareButtonPanel(this, arrowLine)
|
||||
val refillPriority = SmallSquareButtonPanel(this, arrowLine)
|
||||
val autoRefillMatchMode = SmallSquareButtonPanel(this, arrowLine, skinElement = { REFILL_MATCH_MODE[menu.settings.ingredientMatchingMode.ordinal] }) {
|
||||
when (it) {
|
||||
0 -> menu.settings.ingredientMatchingMode = menu.settings.ingredientMatchingMode.next
|
||||
1 -> menu.settings.ingredientMatchingMode = menu.settings.ingredientMatchingMode.prev
|
||||
}
|
||||
|
||||
menu.sendSettingsToServer()
|
||||
}
|
||||
|
||||
val refillPriority = object : SmallSquareButtonPanel(this@ItemMonitorScreen, arrowLine, lambdaOnPress = {
|
||||
when (it) {
|
||||
0 -> menu.settings.ingredientPriority = menu.settings.ingredientPriority.next
|
||||
1 -> menu.settings.ingredientPriority = menu.settings.ingredientPriority.prev
|
||||
}
|
||||
|
||||
menu.sendSettingsToServer()
|
||||
}) {
|
||||
override fun innerRender(stack: PoseStack, mouse_x: Float, mouse_y: Float, flag: Float) {
|
||||
super.innerRender(stack, mouse_x, mouse_y, flag)
|
||||
|
||||
when (menu.settings.ingredientPriority) {
|
||||
ItemMonitorPlayerSettings.Target.SYSTEM -> ARROW.render(stack, 0f, 0f, width, height, UVWindingOrder.FLIP)
|
||||
ItemMonitorPlayerSettings.Target.INVENTORY -> ARROW.render(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refillPriority.setDockMargin(2f)
|
||||
|
||||
@ -101,8 +126,32 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
|
||||
}
|
||||
}
|
||||
|
||||
val whereToPutResult = SmallSquareButtonPanel(this, resultAndButtons, y = 8f)
|
||||
val howMuchToCraft = SmallSquareButtonPanel(this, resultAndButtons, x = 10f, y = 8f)
|
||||
object : SmallSquareButtonPanel(this@ItemMonitorScreen, resultAndButtons, y = 8f, lambdaOnPress = {
|
||||
when (it) {
|
||||
0 -> menu.settings.resultTarget = menu.settings.resultTarget.next
|
||||
1 -> menu.settings.resultTarget = menu.settings.resultTarget.prev
|
||||
}
|
||||
|
||||
menu.sendSettingsToServer()
|
||||
}) {
|
||||
override fun innerRender(stack: PoseStack, mouse_x: Float, mouse_y: Float, flag: Float) {
|
||||
super.innerRender(stack, mouse_x, mouse_y, flag)
|
||||
|
||||
when (menu.settings.resultTarget) {
|
||||
ItemMonitorPlayerSettings.Target.SYSTEM -> ARROW.render(stack, 0f, 0f, width, height, UVWindingOrder.FLIP)
|
||||
ItemMonitorPlayerSettings.Target.INVENTORY -> ARROW.render(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SmallSquareButtonPanel(this, resultAndButtons, x = 10f, y = 8f, skinElement = { HOW_MUCH_TO_CRAFT[menu.settings.craftingAmount.ordinal] }) {
|
||||
when (it) {
|
||||
0 -> menu.settings.craftingAmount = menu.settings.craftingAmount.next
|
||||
1 -> menu.settings.craftingAmount = menu.settings.craftingAmount.prev
|
||||
}
|
||||
|
||||
menu.sendSettingsToServer()
|
||||
}
|
||||
|
||||
val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3)
|
||||
craftingHistory.dock = Dock.LEFT
|
||||
@ -133,5 +182,14 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
|
||||
|
||||
const val ITEM_GRID_WIDTH = 9
|
||||
const val ITEM_GRID_HEIGHT = 5
|
||||
|
||||
val ARROW = SkinGrid.WIDGETS_8[1, 0]
|
||||
val S = SkinGrid.WIDGETS_8[2, 0]
|
||||
val F = SkinGrid.WIDGETS_8[3, 0]
|
||||
val E = SkinGrid.WIDGETS_8[4, 0]
|
||||
val ONE = SkinGrid.WIDGETS_8[5, 0]
|
||||
|
||||
val HOW_MUCH_TO_CRAFT = listOf(ONE, S, F)
|
||||
val REFILL_MATCH_MODE = listOf(F, E)
|
||||
}
|
||||
}
|
||||
|
@ -61,8 +61,8 @@ abstract class SquareButtonPanel(
|
||||
y: Float = 0f,
|
||||
width: Float,
|
||||
height: Float,
|
||||
var skinElement: SkinElement? = null,
|
||||
val lambdaOnPress: (() -> Unit)? = null,
|
||||
val skinElement: (() -> SkinElement)? = null,
|
||||
val lambdaOnPress: ((clickButton: Int) -> Unit)? = null,
|
||||
) : EditablePanel(screen, parent, x, y, width, height) {
|
||||
protected var pressed = false
|
||||
|
||||
@ -91,7 +91,7 @@ abstract class SquareButtonPanel(
|
||||
IDLE.render(stack, 0f, 0f, width, height)
|
||||
}
|
||||
|
||||
skinElement?.render(stack, 0f, 0f, width, height)
|
||||
skinElement?.invoke()?.render(stack, 0f, 0f, width, height)
|
||||
}
|
||||
|
||||
override fun mouseClickedInner(mouse_x: Double, mouse_y: Double, mouse_click_type: Int): Boolean {
|
||||
@ -112,7 +112,7 @@ abstract class SquareButtonPanel(
|
||||
pressed = false
|
||||
|
||||
if (isHovered) {
|
||||
lambdaOnPress?.invoke()
|
||||
lambdaOnPress?.invoke(flag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,8 +129,8 @@ open class LargeSquareButtonPanel(
|
||||
y: Float = 0f,
|
||||
width: Float = SIZE,
|
||||
height: Float = SIZE,
|
||||
skinElement: SkinElement? = null,
|
||||
lambdaOnPress: (() -> Unit)? = null,
|
||||
skinElement: (() -> SkinElement)? = null,
|
||||
lambdaOnPress: ((clickButton: Int) -> Unit)? = null,
|
||||
) : SquareButtonPanel(screen, parent, x, y, width, height, skinElement, lambdaOnPress) {
|
||||
final override val IDLE = SkinGrid.WIDGETS_18[0, 0]
|
||||
final override val HOVERED = SkinGrid.WIDGETS_18[1, 0]
|
||||
@ -148,8 +148,8 @@ open class SmallSquareButtonPanel(
|
||||
y: Float = 0f,
|
||||
width: Float = SIZE,
|
||||
height: Float = SIZE,
|
||||
skinElement: SkinElement? = null,
|
||||
lambdaOnPress: (() -> Unit)? = null,
|
||||
skinElement: (() -> SkinElement)? = null,
|
||||
lambdaOnPress: ((clickButton: Int) -> Unit)? = null,
|
||||
) : SquareButtonPanel(screen, parent, x, y, width, height, skinElement, lambdaOnPress) {
|
||||
final override val IDLE = SkinGrid.WIDGETS_8[0, 0]
|
||||
final override val HOVERED = SkinGrid.WIDGETS_8[0, 0]
|
||||
|
@ -1,12 +1,17 @@
|
||||
package ru.dbotthepony.mc.otm.menu
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraftforge.network.PacketDistributor
|
||||
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity
|
||||
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewSupplier
|
||||
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
|
||||
import ru.dbotthepony.mc.otm.network.MatteryNetworking
|
||||
import ru.dbotthepony.mc.otm.orThrow
|
||||
import ru.dbotthepony.mc.otm.registry.MMenus
|
||||
import ru.dbotthepony.mc.otm.storage.*
|
||||
|
||||
@ -20,6 +25,9 @@ class ItemMonitorMenu @JvmOverloads constructor(
|
||||
private val subscribed: VirtualComponent<ItemStackWrapper>?
|
||||
private val local: PoweredVirtualComponent<ItemStackWrapper>?
|
||||
|
||||
val settings: ItemMonitorPlayerSettings = tile?.getSettings(inventory.player as ServerPlayer) ?: ItemMonitorPlayerSettings()
|
||||
private var settingsNetworked = false
|
||||
|
||||
override fun getNetworkedItemView(): NetworkedItemView {
|
||||
return view
|
||||
}
|
||||
@ -27,7 +35,7 @@ class ItemMonitorMenu @JvmOverloads constructor(
|
||||
init {
|
||||
if (tile != null) {
|
||||
subscribed = tile.cell.storageGraph!!.getVirtualComponent(ITEM_STORAGE)
|
||||
local = PoweredVirtualComponent(subscribed, tile.getCapability(MatteryCapability.ENERGY).resolve().get())
|
||||
local = PoweredVirtualComponent(subscribed, tile.getCapability(MatteryCapability.ENERGY).orThrow())
|
||||
view.setComponent(local)
|
||||
} else {
|
||||
subscribed = null
|
||||
@ -37,6 +45,16 @@ class ItemMonitorMenu @JvmOverloads constructor(
|
||||
addInventorySlots()
|
||||
}
|
||||
|
||||
override fun broadcastFullState() {
|
||||
super.broadcastFullState()
|
||||
MatteryNetworking.CHANNEL.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings)
|
||||
settingsNetworked = true
|
||||
}
|
||||
|
||||
fun sendSettingsToServer() {
|
||||
MatteryNetworking.CHANNEL.sendToServer(settings)
|
||||
}
|
||||
|
||||
override fun removed(p_38940_: Player) {
|
||||
super.removed(p_38940_)
|
||||
view.removed()
|
||||
@ -46,6 +64,11 @@ class ItemMonitorMenu @JvmOverloads constructor(
|
||||
override fun broadcastChanges() {
|
||||
super.broadcastChanges()
|
||||
view.network()
|
||||
|
||||
if (!settingsNetworked) {
|
||||
MatteryNetworking.CHANNEL.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings)
|
||||
settingsNetworked = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWorkingSlotStart(): Int {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 731 B |
BIN
src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf
(Stored with Git LFS)
BIN
src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue
Block a user