Skip to content

Configuration does not accept objects of type class org.bukkit.Material #562

@ValeraShimchuck

Description

@ValeraShimchuck

Hello, I have an issue with configurate. When I save my config-class I get this error:

 java.lang.IllegalArgumentException: Configuration does not accept objects of type class org.bukkit.Material
[11:31:40 WARN]:        at org.spongepowered.configurate.ScalarConfigValue.set(ScalarConfigValue.java:45)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.insertNewValue(AbstractConfigurationNode.java:351)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.raw(AbstractConfigurationNode.java:427)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.from(AbstractConfigurationNode.java:308)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractCommentedConfigurationNode.from(AbstractCommentedConfigurationNode.java:71)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractCommentedConfigurationNode.from(AbstractCommentedConfigurationNode.java:25)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.from(AbstractConfigurationNode.java:301)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractCommentedConfigurationNode.from(AbstractCommentedConfigurationNode.java:71)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractCommentedConfigurationNode.from(AbstractCommentedConfigurationNode.java:25)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.set(AbstractConfigurationNode.java:250)
[11:31:40 WARN]:        at org.spongepowered.configurate.AbstractConfigurationNode.set(AbstractConfigurationNode.java:45)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ConfigurationNodeSerializer.serialize(ConfigurationNodeSerializer.java:47)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ConfigurationNodeSerializer.serialize(ConfigurationNodeSerializer.java:33)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.TypeSerializer.serialize(TypeSerializer.java:121)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.saveSingle(ObjectMapperImpl.java:173)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.save(ObjectMapperImpl.java:144)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperFactoryImpl.serialize(ObjectMapperFactoryImpl.java:277)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.TypeSerializer.serialize(TypeSerializer.java:121)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.lambda$serialize$0(AbstractListChildSerializer.java:93)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:52)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:28)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.serialize(AbstractListChildSerializer.java:90)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.saveSingle(ObjectMapperImpl.java:173)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.save(ObjectMapperImpl.java:144)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperFactoryImpl.serialize(ObjectMapperFactoryImpl.java:277)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.TypeSerializer.serialize(TypeSerializer.java:121)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.lambda$serialize$0(AbstractListChildSerializer.java:93)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:52)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:28)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.serialize(AbstractListChildSerializer.java:90)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.saveSingle(ObjectMapperImpl.java:173)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.save(ObjectMapperImpl.java:144)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperFactoryImpl.serialize(ObjectMapperFactoryImpl.java:277)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.TypeSerializer.serialize(TypeSerializer.java:121)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.lambda$serialize$0(AbstractListChildSerializer.java:93)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:52)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.ListSerializer.forEachElement(ListSerializer.java:28)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.AbstractListChildSerializer.serialize(AbstractListChildSerializer.java:90)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.saveSingle(ObjectMapperImpl.java:173)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.save(ObjectMapperImpl.java:144)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperFactoryImpl.serialize(ObjectMapperFactoryImpl.java:277)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.TypeSerializer.serialize(TypeSerializer.java:121)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.MapSerializer.serialize(MapSerializer.java:117)
[11:31:40 WARN]:        at org.spongepowered.configurate.serialize.MapSerializer.serialize(MapSerializer.java:35)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.saveSingle(ObjectMapperImpl.java:173)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperImpl.save(ObjectMapperImpl.java:144)
[11:31:40 WARN]:        at org.spongepowered.configurate.objectmapping.ObjectMapperFactoryImpl.serialize(ObjectMapperFactoryImpl.java:277)
[11:31:40 WARN]:        at org.spongepowered.configurate.ScopedConfigurationNode.set(ScopedConfigurationNode.java:120)
[11:31:40 WARN]:        at org.spongepowered.configurate.ScopedConfigurationNode.set(ScopedConfigurationNode.java:168)
[11:31:40 WARN]:        at org.spongepowered.configurate.ScopedConfigurationNode.set(ScopedConfigurationNode.java:43)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader.save(ConfigLoader.kt:64)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader$ContextLoader.save(ConfigLoader.kt:126)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader.loadOrSave$lambda$1(ConfigLoader.kt:30)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader.applyContext(ConfigLoader.kt:24)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader.loadOrSave(ConfigLoader.kt:28)
[11:31:40 WARN]:        at libs.jar//host.godlike.mc.monorepo.library.configuration.v2.ConfigLoader.loadOrSave(ConfigLoader.kt:38)

Material class is an enum, but the lib doesn't recognize it and can't store. Though, the ObjectMapper doesn't complain whenever I load the config.

My configuration class:

package host.godlike.mc.monorepo.cases.entity

import host.godlike.mc.monorepo.cases.common.config.RawItemStack
import host.godlike.mc.monorepo.cases.common.item.replaceText
import host.godlike.mc.monorepo.cases.common.random.RangedRandom
import host.godlike.mc.monorepo.library.configuration.utils.text.RawComponent
import net.kyori.adventure.text.Component
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.inventory.ItemFlag
import org.bukkit.inventory.ItemStack
import org.spongepowered.configurate.CommentedConfigurationNode
import org.spongepowered.configurate.ConfigurationNode
import org.spongepowered.configurate.objectmapping.ConfigSerializable
import org.spongepowered.configurate.objectmapping.meta.Setting
import ua.valeriishymchuk.simpleitemgenerator.api.SimpleItemGenerator
import kotlin.math.min
import kotlin.random.Random

@ConfigSerializable
class ConfigEntity {

    val permissions: PermissionsSection = PermissionsSection()

    val previewCase = PreviewCaseSection()

    @ConfigSerializable
    class PermissionsSection {
        val reload: String = "gcases.commands.reload"
        val openCase: String = "gcases.commands.open"
        val openCaseWithoutKey: String = "gcases.commands.open.withoutkey"
    }

    @ConfigSerializable
    class PreviewCaseSection {

        val inventoryMap: List<String> = listOf(
            "bbbbbbbbb",
            "bgggggggb",
            "bgggggggb",
            "bgggggggb",
            "bgggggggb",
            "bbbbbbbbc"
        )
        val itemsMap: Map<Char, RawItemStack> = mapOf(
            'b' to RawItemStack(
                Material.BLACK_STAINED_GLASS_PANE,
                displayName = RawComponent(" ")
            ),
            'g' to RawItemStack(
                Material.GRAY_STAINED_GLASS_PANE,
                displayName = RawComponent(" ")
            ),
            'c' to RawItemStack(
                Material.BARRIER,
                displayName = RawComponent("Back")
            )
        )
        val characterItemPlaceholder = 'g'
        val closeButton: Int = 53
        val closeCommand: List<String> = listOf(
            "msg %player% closing"
        )
        val inventoryName: String = "Case"
    }

    val caseSpeed: Map<Int, Double> = mapOf(
        0 to 2.0,
        15 to 1.0,
        40 to 0.5,
        100 to 0.05
    )

    val caseTicks: Int = 220
    val caseTicksRandomOffset: Int = 30
    val winDelay: Int = 20
    val rewardBlinkPeriod = 10;

    val rarities: Map<String, RaritySection> = mapOf(
        "common" to RaritySection(sound = Sound.ENTITY_EXPERIENCE_ORB_PICKUP, weight = 1),
        "rare" to RaritySection(sound = Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1.6f, item = RawItemStack(material = Material.BLUE_STAINED_GLASS_PANE, displayName = RawComponent(" ")), loreAddition = RawComponent("<white>Rarity: <blue><bold>RARE"), weight = 2),
        "mythic" to RaritySection(sound = Sound.BLOCK_ENCHANTMENT_TABLE_USE, item = RawItemStack(material = Material.MAGENTA_STAINED_GLASS_PANE), loreAddition = RawComponent("<white>Rarity: <dark_purple><bold>MYTHIC"), weight = 3),
        "legendary" to RaritySection(sound = Sound.UI_TOAST_CHALLENGE_COMPLETE, item = RawItemStack(material = Material.YELLOW_STAINED_GLASS_PANE), loreAddition = RawComponent("<white>Rarity: <GOLD><bold>LEGENDARY"), weight = 4)
    )

    @ConfigSerializable
    data class RaritySection(
        val sound: Sound = Sound.BLOCK_NOTE_BLOCK_PLING,
        val pitch: Float = 1.0f,
        val volume: Float = 1.0f,
        val item: RawItemStack = RawItemStack(
            Material.LIME_STAINED_GLASS_PANE,
            displayName = RawComponent(" ")
        ),
        val loreAddition: RawComponent = RawComponent("<white>Rarity: <green><bold>COMMON"),
        val weight: Int
    ) {
        val buildSound: net.kyori.adventure.sound.Sound get() = net.kyori.adventure.sound.Sound.sound(sound, net.kyori.adventure.sound.Sound.Source.MASTER, volume, pitch)

        private constructor() : this(sound = Sound.BLOCK_NOTE_BLOCK_PLING, pitch = 1.0f, volume = 1.0f, weight = 0)
    }

    fun totalOffsetAtMoment(tick: Int, caseTicks: Int): Int {
        var ticksLeft = tick
        var lastSpeedTick = 0
        //var lastSpeed = caseSpeed[0] ?: 1.0
        var lastSpeed = 1.0
        var totalOffset = 0
        caseSpeed.toSortedMap().forEach { (key, value) ->
            if (ticksLeft <= 0) {
                return@forEach
            }
            val tickDuration: Int = key - lastSpeedTick
            val ticksToHandle: Int = min(ticksLeft, tickDuration)
            ticksLeft -= ticksToHandle
            totalOffset += (ticksToHandle * lastSpeed).toInt()
            lastSpeed = value
            lastSpeedTick = key
        }
        if (ticksLeft > 0) {
            val tickDuration: Int = caseTicks - lastSpeedTick
            val ticksToHandle: Int = min(ticksLeft, tickDuration)
            totalOffset += (ticksToHandle * lastSpeed).toInt()
        }
        return totalOffset
    }

    fun getItemAtIndex(seed: Long, index: Int, caseRouletteInfo: CaseRouletteInfo): ItemStack {
        val random = Random(seed + index)
        val group = caseRouletteInfo.generateGroup(random)
        return group.bakeItem(group.generateItem(random))
    }

    fun getRareItemAtIndex(seed: Long, index: Int, caseRouletteInfo: CaseRouletteInfo): ItemStack {
        val group = getGroupAtIndex(seed, index, caseRouletteInfo)
        val raritySection: RaritySection = rarities[group.rarityKey] ?: throw IllegalStateException("Unknown rarity: ${group.rarityKey}")
        return raritySection.item.itemStack { it }
    }

    fun getItemChanceAtIndex(seed: Long, index: Int, caseRouletteInfo: CaseRouletteInfo): ChanceItem {
        val random = Random(seed + index)
        val group = caseRouletteInfo.generateGroup(random)
        return group.generateItem(random)
    }

    fun getGroupAtIndex(seed: Long, index: Int, caseRouletteInfo: CaseRouletteInfo): ChanceGroup {
        val random = Random(seed + index)
        return caseRouletteInfo.generateGroup(random)
    }

    fun frame(seed: Long, caseRouletteInfo: CaseRouletteInfo, tick: Int, caseTicks: Int): List<ItemStack> {
        val frameLength: Int = caseRouletteInfo.casesView.distinct().size
        val offset = totalOffsetAtMoment(tick, caseTicks)
        val offsetIndices: List<Int> = (0..<frameLength).map {
            it + offset
        }
        return offsetIndices.map { getItemAtIndex(seed, it, caseRouletteInfo) }
    }

    fun rarityFrame(seed: Long, caseRouletteInfo: CaseRouletteInfo, tick: Int, caseTicks: Int): List<ItemStack> {
        val frameLength: Int = caseRouletteInfo.casesView.distinct().size
        val offset = totalOffsetAtMoment(tick, caseTicks)
        val offsetIndices: List<Int> = (0..<frameLength).map {
            it + offset
        }
        return offsetIndices.map { getRareItemAtIndex(seed, it, caseRouletteInfo) }
    }

    fun getWinChanceItem(seed: Long, caseTicks: Int, caseRouletteInfo: CaseRouletteInfo): ChanceItem {
        val prizeOffset = caseRouletteInfo.localRewardIndex
        val totalOffset = totalOffsetAtMoment(caseTicks, caseTicks)
        return getItemChanceAtIndex(seed, prizeOffset + totalOffset, caseRouletteInfo)
    }

    fun getWinGroup(seed: Long, caseTicks: Int, caseRouletteInfo: CaseRouletteInfo): ChanceGroup {
        val prizeOffset = caseRouletteInfo.localRewardIndex
        val totalOffset = totalOffsetAtMoment(caseTicks, caseTicks)
        return getGroupAtIndex(seed, prizeOffset + totalOffset, caseRouletteInfo)
    }

    val cases: Map<String, Case> = mapOf(
        "example" to
                Case(
                    27,
                    listOf(
                        CaseRouletteInfo(
                            13,
                            listOf(10, 11, 12, 13, 14, 15, 16),
                            listOf(
                                ChanceGroup(
                                    RawComponent("common"),
                                    50.0,
                                    arrayListOf(
                                        ChanceItem(
                                            RawItemStack(
                                                Material.DIRT,
                                                1,
                                                RawComponent("THE DIRT"),
                                                RawComponent(
                                                    "Just a dirt and no more",
                                                    "Rarity: %rarity%"
                                                ),
                                                ItemFlag.entries
                                            ),
                                            1.0,
                                            listOf("give %player% dirt")
                                        ),
                                        ChanceItem(
                                            RawItemStack(
                                                Material.GRAVEL,
                                                1,
                                                RawComponent("THE GRAVEL"),
                                                RawComponent(
                                                    "Just a gravel and no more",
                                                    "Rarity: %rarity%"
                                                ),
                                                null
                                            ),
                                            1.0,
                                            listOf("give %player% gravel")
                                        )
                                    )
                                ),
                                ChanceGroup(
                                    RawComponent("uncommon"),
                                    30.0,
                                    arrayListOf(
                                        ChanceItem(
                                            RawItemStack(
                                                Material.GOLD_BLOCK,
                                                1,
                                                RawComponent("THE GOLD BLOCK"),
                                                RawComponent(
                                                    "Precious gold block, you'll like it",
                                                    "Rarity: %rarity%"
                                                ),
                                                null
                                            ),
                                            1.0,
                                            listOf("give %player% gold_block")
                                        ),
                                        ChanceItem(
                                            RawItemStack(
                                                Material.IRON_BLOCK,
                                                1,
                                                RawComponent("THE IRON BLOCK"),
                                                RawComponent(
                                                    "Just an iron block and no more",
                                                    "Rarity: %rarity%"
                                                ),
                                                null
                                            ),
                                            3.0,
                                            listOf("give %player% iron_block")
                                        )
                                    )
                                ),
                                ChanceGroup(
                                    RawComponent("rare"),
                                    20.0,
                                    arrayListOf(
                                        ChanceItem(
                                            RawItemStack(
                                                Material.DIAMOND_BLOCK,
                                                1,
                                                RawComponent("THE DIAMOND BLOCK"),
                                                RawComponent(
                                                    "Precious diamond block, you'll like it",
                                                    "Rarity: %rarity%"
                                                ),
                                                null
                                            ),
                                            9.0,
                                            listOf("give %player% diamond_block")
                                        ),
                                        ChanceItem(
                                            RawItemStack(
                                                Material.NETHERITE_BLOCK,
                                                1,
                                                RawComponent("THE ULTIMATE NETHERITE BLOCK"),
                                                RawComponent(
                                                    "You lucky if you've got it",
                                                    "Rarity: %rarity%"
                                                ),
                                                null
                                            ),
                                            1.0,
                                            listOf("give %player% netherite_block")
                                        )
                                    )
                                )
                            )
                        )
                    ),
                    listOf(
                        "xxxxrxxxx",
                        "x       x",
                        "xxxxrxxxo"
                    ),
                    mapOf(
                        'x' to ItemStack(Material.BLACK_STAINED_GLASS_PANE, 1).apply {
                            itemMeta = itemMeta.apply {
                                displayName(Component.text(" "))
                            }
                        },
                        'r' to ItemStack(Material.RED_STAINED_GLASS_PANE, 1).apply {
                            itemMeta = itemMeta.apply {
                                displayName(Component.text(" "))
                            }
                        },
                        'o' to ItemStack(Material.BARRIER, 1).apply {
                            itemMeta = itemMeta.apply {
                                displayName(Component.text("Close"))
                            }
                        }
                    ).mapValues { entry -> RawItemStack.fromItemStack(entry.value) },
                    26,
                    "gkeys:emerald_key"
                )
    )

    @ConfigSerializable
    data class CaseSpeedEntry(
        val tick: Int,
        val elementsPerTick: Double
    ) {

        private constructor() : this(0, 0.0)

    }

    @ConfigSerializable
    data class Case(
        val inventorySize: Int,
        val cases: List<CaseRouletteInfo>,
        val inventoryMap: List<String>,
        val itemsMap: Map<Char, RawItemStack>,
        val closeButton: Int,
        val keyItem: String,
        val inventoryName: String = "Case",
        val pointerIndices: List<Int> = listOf(4, 22),
        val droppedItemPointer: RawItemStack = RawItemStack(
            Material.GREEN_STAINED_GLASS_PANE,
            displayName = RawComponent("")
        )
    ) {
        private constructor() : this(9, emptyList(), emptyList(), emptyMap(), 0, "")

        val inventory: List<ItemStack>
            get() = inventoryMap.joinToString("").toCharArray().map {
                itemsMap[it] ?: RawItemStack.DEFAULT
            }.map { it.itemStack { msg -> msg } }

    }

    @ConfigSerializable
    data class CaseRouletteInfo(
        val takeButton: Int,
        val casesView: List<Int>,
        val items: List<ChanceGroup>,
        val rarityView: List<List<Int>> = listOf(
            listOf(1, 2, 3, 4, 5, 6, 7),
            listOf(19, 20, 21, 22, 23, 24, 25)
        )
    ) {

        private constructor() : this(0, arrayListOf(), arrayListOf())

        val localRewardIndex: Int get() = casesView.indexOf(takeButton)

        fun generateGroup(random: Random): ChanceGroup {
            val rangedRandom: RangedRandom<ChanceGroup> = RangedRandom(random, items.map { it to it.randomWeight })
            return rangedRandom.generate()
        }

    }

    @ConfigSerializable
    data class ChanceGroup(val rarityDisplay: RawComponent, val randomWeight: Double, val items: List<ChanceItem>, val rarityKey: String = "common") {

        private constructor() : this(RawComponent(), 1.0, emptyList())

        fun generateItem(random: Random): ChanceItem {
            val rangedRandom: RangedRandom<ChanceItem> = RangedRandom(random, items.map { it to it.randomWeight })
            return rangedRandom.generate()
        }

        fun bakeItem(item: ChanceItem): ItemStack = item.icon.replaceText {
            it.replaceText("%rarity%", rarityDisplay)

        }


    }

    @ConfigSerializable
    data class ChanceItem(
        @Setting("icon") val rawIcon: ConfigurationNode,
        val randomWeight: Double,
        val commands: List<String>
    ) {

        constructor(icon: RawItemStack, weight: Double, commands: List<String>) : this(icon.let {
            val node = CommentedConfigurationNode.root()
            node.set(icon)
        }, weight, commands)

        private constructor() : this(CommentedConfigurationNode.root(), 1.0, emptyList())

        @delegate:Transient
        val icon: ItemStack by lazy {
            if (rawIcon.isNull) throw IllegalArgumentException("Icon is null")
            if (rawIcon.isMap) {
                return@lazy rawIcon.get(RawItemStack::class.java)!!.itemStack { it }
            }
            val key: String = rawIcon.get(String::class.java)!!
            val split = key.split(":")
            val firstKey: String = split.first()
            val amount: Int? = split.getOrNull(1)?.toInt()
            SimpleItemGenerator.get().bakeItem(firstKey, null)
                .orElseThrow { IllegalArgumentException("Key $key not found") }
                .also { item -> amount?.let { item.amount = it } }
        }


        fun replaceCommands(player: String): List<String> = commands.map { it.replace("%player%", player) }

    }

}

Is there any way to get more information about this error? Because it's not helpful.

Lib version: 4.2.0-SNAPSHOT

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions