mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 03:24:06 +09:00
crafting recipe loader wip
This commit is contained in:
@@ -9,7 +9,7 @@ Multiple workbenches are separated by commas, and alternative workbenches are se
|
|||||||
|
|
||||||
### Ingredient Querying
|
### Ingredient Querying
|
||||||
|
|
||||||
Ingredients are defined as list of records. Multiple records denotes multiple alternative recipes, whereas
|
Ingredients are defined as list of records. Multiple records denote multiple alternative recipes, whereas
|
||||||
entries in a record denote multiple ingredients the recipe requires.
|
entries in a record denote multiple ingredients the recipe requires.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -25,18 +25,18 @@ Each entry is interpreted as:
|
|||||||
|
|
||||||
```[moq, count 1, ingredient 1, count 2, ingredient 2, ...]```
|
```[moq, count 1, ingredient 1, count 2, ingredient 2, ...]```
|
||||||
|
|
||||||
- moq: this item combination creates this amount of items.
|
- moq: this combination of ingredients creates this amount of crafted item.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```[2, 1, "$WOOD", 1, "$ROCK"]```
|
```[2, 1, "$WOOD", 1, "$ROCK"]```
|
||||||
|
|
||||||
This line is interpreted as: this item requires 1 tagged-as-wood ingredient and 1 tagged-as-rock ingredient,
|
This line is interpreted as: this item requires 1 tagged-as-wood ingredient and 1 tagged-as-rock ingredient,
|
||||||
and returns 2 of manufactured items.
|
and returns 2 of crafted items.
|
||||||
|
|
||||||
```[20, 1, "ITEM_PLATFORM_BUILDING_KIT"]```
|
```[20, 1, "ITEM_PLATFORM_BUILDING_KIT"]```
|
||||||
|
|
||||||
This line is interpreted as: this item requires 1 verbatim item "ITEM_PLATFORM_BUILDING_KIT" and returns
|
This line is interpreted as: this item requires 1 verbatim item "ITEM_PLATFORM_BUILDING_KIT" and returns
|
||||||
20 of manufactured items.
|
20 of crafted items.
|
||||||
|
|
||||||
Therefore, the single record has at least three items and always has odd number of items.
|
Therefore, the single record has at least three elements and always has odd number of them.
|
||||||
@@ -8,6 +8,7 @@ import net.torvald.terrarum.blockproperties.BlockCodex
|
|||||||
import net.torvald.terrarum.blockproperties.WireCodex
|
import net.torvald.terrarum.blockproperties.WireCodex
|
||||||
import net.torvald.terrarum.gameitems.GameItem
|
import net.torvald.terrarum.gameitems.GameItem
|
||||||
import net.torvald.terrarum.gameitems.ItemID
|
import net.torvald.terrarum.gameitems.ItemID
|
||||||
|
import net.torvald.terrarum.itemproperties.CraftingCodex
|
||||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||||
import net.torvald.terrarum.itemproperties.MaterialCodex
|
import net.torvald.terrarum.itemproperties.MaterialCodex
|
||||||
import net.torvald.terrarum.langpack.Lang
|
import net.torvald.terrarum.langpack.Lang
|
||||||
@@ -224,7 +225,7 @@ object ModMgr {
|
|||||||
|
|
||||||
// check for module-info.java
|
// check for module-info.java
|
||||||
val moduleInfoPath = cl.getResources("module-info.class").toList().filter { it.toString().contains("$moduleName/$jar!/module-info.class") && it.toString().endsWith("module-info.class")}
|
val moduleInfoPath = cl.getResources("module-info.class").toList().filter { it.toString().contains("$moduleName/$jar!/module-info.class") && it.toString().endsWith("module-info.class")}
|
||||||
if (moduleInfoPath.size == 0) {
|
if (moduleInfoPath.isEmpty()) {
|
||||||
throw IllegalStateException("module-info not found on $moduleName")
|
throw IllegalStateException("module-info not found on $moduleName")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +442,7 @@ object ModMgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object GameItemLoader {
|
object GameItemLoader {
|
||||||
val itemPath = "items/"
|
const val itemPath = "items/"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Terrarum.itemCodex = ItemCodex()
|
Terrarum.itemCodex = ItemCodex()
|
||||||
@@ -488,7 +489,7 @@ object ModMgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object GameLanguageLoader {
|
object GameLanguageLoader {
|
||||||
val langPath = "locales/"
|
const val langPath = "locales/"
|
||||||
|
|
||||||
@JvmStatic operator fun invoke(module: String) {
|
@JvmStatic operator fun invoke(module: String) {
|
||||||
Lang.load(getFile(module, langPath))
|
Lang.load(getFile(module, langPath))
|
||||||
@@ -496,7 +497,7 @@ object ModMgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object GameMaterialLoader {
|
object GameMaterialLoader {
|
||||||
val matePath = "materials/"
|
const val matePath = "materials/"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Terrarum.materialCodex = MaterialCodex()
|
Terrarum.materialCodex = MaterialCodex()
|
||||||
@@ -511,7 +512,7 @@ object ModMgr {
|
|||||||
* A sugar-library for easy texture pack creation
|
* A sugar-library for easy texture pack creation
|
||||||
*/
|
*/
|
||||||
object GameRetextureLoader {
|
object GameRetextureLoader {
|
||||||
val retexturesPath = "retextures/"
|
const val retexturesPath = "retextures/"
|
||||||
val retexables = listOf("blocks","wires")
|
val retexables = listOf("blocks","wires")
|
||||||
val altFilePaths = HashMap<String, FileHandle>()
|
val altFilePaths = HashMap<String, FileHandle>()
|
||||||
val retexableCallbacks = HashMap<String, () -> Unit>()
|
val retexableCallbacks = HashMap<String, () -> Unit>()
|
||||||
@@ -537,8 +538,8 @@ object ModMgr {
|
|||||||
|
|
||||||
if (dir.isDirectory && dir.exists()) {
|
if (dir.isDirectory && dir.exists()) {
|
||||||
dir.listFiles { it: File ->
|
dir.listFiles { it: File ->
|
||||||
it?.name?.contains('-') == true
|
it.name.contains('-')
|
||||||
}.forEach {
|
}?.forEach {
|
||||||
// <other modname>-<hopefully a number>.tga or .png
|
// <other modname>-<hopefully a number>.tga or .png
|
||||||
val tokens = it.name.split('-')
|
val tokens = it.name.split('-')
|
||||||
if (tokens.size > 1) {
|
if (tokens.size > 1) {
|
||||||
@@ -558,6 +559,16 @@ object ModMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object GameCraftingRecipeLoader {
|
||||||
|
const val recipePath = "crafting/"
|
||||||
|
|
||||||
|
@JvmStatic operator fun invoke(module: String) {
|
||||||
|
getFile(module, recipePath).listFiles { it: File -> it.name.lowercase().endsWith(".json") }?.forEach { jsonFile ->
|
||||||
|
Terrarum.craftingCodex.addFromJson(JsonFetcher(jsonFile), module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class JarFileLoader(urls: Array<URL>) : URLClassLoader(urls) {
|
private class JarFileLoader(urls: Array<URL>) : URLClassLoader(urls) {
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ object PostProcessor : Disposable {
|
|||||||
|
|
||||||
gdxClearAndSetBlend(.094f, .094f, .094f, 0f)
|
gdxClearAndSetBlend(.094f, .094f, .094f, 0f)
|
||||||
|
|
||||||
|
fbo.colorBufferTexture.setFilter(
|
||||||
|
Texture.TextureFilter.Linear,
|
||||||
|
if (App.scr.magn % 1.0 < 0.0001) Texture.TextureFilter.Nearest else Texture.TextureFilter.Linear
|
||||||
|
)
|
||||||
|
|
||||||
postShader(projMat, fbo)
|
postShader(projMat, fbo)
|
||||||
|
|
||||||
// draw things when F keys are on
|
// draw things when F keys are on
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import net.torvald.terrarum.gameactors.Actor
|
|||||||
import net.torvald.terrarum.gameactors.ActorID
|
import net.torvald.terrarum.gameactors.ActorID
|
||||||
import net.torvald.terrarum.gameactors.faction.FactionCodex
|
import net.torvald.terrarum.gameactors.faction.FactionCodex
|
||||||
import net.torvald.terrarum.gameworld.fmod
|
import net.torvald.terrarum.gameworld.fmod
|
||||||
|
import net.torvald.terrarum.itemproperties.CraftingCodex
|
||||||
import net.torvald.terrarum.itemproperties.ItemCodex
|
import net.torvald.terrarum.itemproperties.ItemCodex
|
||||||
import net.torvald.terrarum.itemproperties.MaterialCodex
|
import net.torvald.terrarum.itemproperties.MaterialCodex
|
||||||
import net.torvald.terrarum.savegame.ByteArray64Reader
|
import net.torvald.terrarum.savegame.ByteArray64Reader
|
||||||
@@ -72,6 +73,7 @@ object Terrarum : Disposable {
|
|||||||
var wireCodex = WireCodex(); internal set
|
var wireCodex = WireCodex(); internal set
|
||||||
var materialCodex = MaterialCodex(); internal set
|
var materialCodex = MaterialCodex(); internal set
|
||||||
var factionCodex = FactionCodex(); internal set
|
var factionCodex = FactionCodex(); internal set
|
||||||
|
var craftingCodex = CraftingCodex(); internal set
|
||||||
var apocryphas = HashMap<String, Any>(); internal set
|
var apocryphas = HashMap<String, Any>(); internal set
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
102
src/net/torvald/terrarum/itemproperties/CraftingCodex.kt
Normal file
102
src/net/torvald/terrarum/itemproperties/CraftingCodex.kt
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package net.torvald.terrarum.itemproperties
|
||||||
|
|
||||||
|
import com.badlogic.gdx.utils.JsonValue
|
||||||
|
import net.torvald.terrarum.gameitems.ItemID
|
||||||
|
import net.torvald.terrarum.utils.forEachSiblings
|
||||||
|
import net.torvald.terrarum.utils.forEachSiblingsIndexed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2022-06-24.
|
||||||
|
*/
|
||||||
|
class CraftingCodex {
|
||||||
|
|
||||||
|
@Transient private val props = HashMap<ItemID, ArrayList<CraftingRecipe>>()
|
||||||
|
|
||||||
|
fun addFromJson(json: JsonValue, moduleName: String) {
|
||||||
|
|
||||||
|
if (moduleName.filter { it.code in 33..127 } .length < 5)
|
||||||
|
throw IllegalStateException("Invalid module name: ${moduleName}")
|
||||||
|
|
||||||
|
json.forEachSiblings { itemName, details ->
|
||||||
|
val workbenchStr = details["workbench"].asString()
|
||||||
|
val recipes = ArrayList<CraftingRecipe>()
|
||||||
|
|
||||||
|
details["ingredients"].forEachSiblingsIndexed { ingredientIndex, _, ingredientRecord ->
|
||||||
|
var moq = -1L
|
||||||
|
val qtys = ArrayList<Long>()
|
||||||
|
val itemsStr = ArrayList<String>()
|
||||||
|
ingredientRecord.forEachIndexed { i, elem ->
|
||||||
|
if (i == 0) {
|
||||||
|
moq = elem.asLong()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (i % 2 == 1)
|
||||||
|
qtys.add(elem.asLong())
|
||||||
|
else
|
||||||
|
itemsStr.add(elem.asString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if (moq < 1)
|
||||||
|
throw IllegalStateException("Recipe #${ingredientIndex+1} for item '$itemName' has moq of ${moq}")
|
||||||
|
else if (qtys.size != itemsStr.size)
|
||||||
|
throw IllegalStateException("Mismatched item name and count for recipe #${ingredientIndex+1} for item '$itemName'")
|
||||||
|
|
||||||
|
val ingredients = ArrayList<CraftingIngredients>()
|
||||||
|
itemsStr.forEachIndexed { i, itemStr ->
|
||||||
|
ingredients.add(CraftingIngredients(
|
||||||
|
if (itemStr.startsWith("$"))
|
||||||
|
itemStr.substring(1)
|
||||||
|
else
|
||||||
|
itemStr
|
||||||
|
,
|
||||||
|
if (itemStr.startsWith("$"))
|
||||||
|
CraftingItemKeyMode.TAG
|
||||||
|
else
|
||||||
|
CraftingItemKeyMode.VERBATIM
|
||||||
|
,
|
||||||
|
qtys[i]
|
||||||
|
))
|
||||||
|
}
|
||||||
|
recipes.add(CraftingRecipe(workbenchStr, ingredients.toTypedArray(), moq, moduleName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// register to the main props
|
||||||
|
if (props[itemName] == null) props[itemName] = ArrayList()
|
||||||
|
props[itemName]!!.addAll(recipes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of all possible recipes for the item; null if there is no recipe
|
||||||
|
*
|
||||||
|
* Even if the props for the specified item is happens to exist but has no element (usually caused by bad mod behaviour),
|
||||||
|
* this function is guaranteed to return null.
|
||||||
|
*/
|
||||||
|
fun getRecipesFor(itemID: ItemID): List<CraftingRecipe>? = props[itemID]?.toList()?.let {
|
||||||
|
return if (it.isNotEmpty()) it else null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list of itemIDs and corresponding recipes
|
||||||
|
*/
|
||||||
|
fun getRecipesUsingTheseItems(items: List<ItemID>): List<Pair<ItemID, CraftingRecipe>> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list of itemIDs and corresponding recipes
|
||||||
|
*/
|
||||||
|
fun getRecipesForIngredients(ingredients: List<Pair<ItemID, Long>>): List<Pair<ItemID, CraftingRecipe>> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data class CraftingRecipe(val workbench: String, val ingredients: Array<CraftingIngredients>, val moq: Long, val addedBy: String)
|
||||||
|
data class CraftingIngredients(val key: String, val keyMode: CraftingItemKeyMode, val qty: Long)
|
||||||
|
enum class CraftingItemKeyMode { VERBATIM, TAG }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ object Lang {
|
|||||||
* "<<STRING ID>>" = "<<LOCALISED TEXT>>"
|
* "<<STRING ID>>" = "<<LOCALISED TEXT>>"
|
||||||
*/
|
*/
|
||||||
//println(json.entrySet())
|
//println(json.entrySet())
|
||||||
JsonFetcher.forEach(json) { key, value ->
|
JsonFetcher.forEachSiblings(json) { key, value ->
|
||||||
langpack.put("${key}_$lang", value.asString())
|
langpack.put("${key}_$lang", value.asString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ object Lang {
|
|||||||
* (the array continues)
|
* (the array continues)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
JsonFetcher.forEach(json.get("resources").get("data")) { _, entry ->
|
JsonFetcher.forEachSiblings(json.get("resources").get("data")) { _, entry ->
|
||||||
langpack.put(
|
langpack.put(
|
||||||
"${entry.getString("n")}_$lang",
|
"${entry.getString("n")}_$lang",
|
||||||
entry.getString("s")
|
entry.getString("s")
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ object InjectCreatureRaw {
|
|||||||
val jsonObj = JsonFetcher(ModMgr.getFile(module, "creatures/$jsonFileName"))
|
val jsonObj = JsonFetcher(ModMgr.getFile(module, "creatures/$jsonFileName"))
|
||||||
|
|
||||||
|
|
||||||
JsonFetcher.forEach(jsonObj) { key, value -> if (!key.startsWith("_")) {
|
JsonFetcher.forEachSiblings(jsonObj) { key, value -> if (!key.startsWith("_")) {
|
||||||
val diceRollers = ArrayList<String>()
|
val diceRollers = ArrayList<String>()
|
||||||
|
|
||||||
if (!value.isArray && !value.isObject) {
|
if (!value.isArray && !value.isObject) {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ object Common {
|
|||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashArray<*> {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashArray<*> {
|
||||||
val hashMap = HashArray<Any>()
|
val hashMap = HashArray<Any>()
|
||||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
|
||||||
hashMap[key.toLong()] = json.readValue(null, obj)
|
hashMap[key.toLong()] = json.readValue(null, obj)
|
||||||
}
|
}
|
||||||
return hashMap
|
return hashMap
|
||||||
@@ -138,7 +138,7 @@ object Common {
|
|||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWirings {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWirings {
|
||||||
val hashMap = HashedWirings()
|
val hashMap = HashedWirings()
|
||||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
|
||||||
hashMap[key.toLong()] = json.readValue(GameWorld.WiringNode::class.java, obj)
|
hashMap[key.toLong()] = json.readValue(GameWorld.WiringNode::class.java, obj)
|
||||||
}
|
}
|
||||||
return hashMap
|
return hashMap
|
||||||
@@ -156,7 +156,7 @@ object Common {
|
|||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWiringGraph {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWiringGraph {
|
||||||
val hashMap = HashedWiringGraph()
|
val hashMap = HashedWiringGraph()
|
||||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
|
||||||
hashMap[key.toLong()] = json.readValue(WiringGraphMap::class.java, obj)
|
hashMap[key.toLong()] = json.readValue(WiringGraphMap::class.java, obj)
|
||||||
}
|
}
|
||||||
return hashMap
|
return hashMap
|
||||||
@@ -174,7 +174,7 @@ object Common {
|
|||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WiringGraphMap {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WiringGraphMap {
|
||||||
val hashMap = WiringGraphMap()
|
val hashMap = WiringGraphMap()
|
||||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
|
||||||
hashMap[key] = json.readValue(GameWorld.WiringSimCell::class.java, obj)
|
hashMap[key] = json.readValue(GameWorld.WiringSimCell::class.java, obj)
|
||||||
}
|
}
|
||||||
return hashMap
|
return hashMap
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ object WriteConfig {
|
|||||||
|
|
||||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): KVHashMap {
|
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): KVHashMap {
|
||||||
val map = KVHashMap()
|
val map = KVHashMap()
|
||||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
|
||||||
map[key] = json.readValue(null, obj)
|
map[key] = json.readValue(null, obj)
|
||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
|
|||||||
@@ -79,10 +79,10 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
|
|||||||
|
|
||||||
/** Position of mouse relative to this item */
|
/** Position of mouse relative to this item */
|
||||||
protected val itemRelativeMouseX: Int
|
protected val itemRelativeMouseX: Int
|
||||||
get() = (Terrarum.mouseScreenX - (parentUI.posX) - this.posX)
|
get() = (Terrarum.mouseScreenX - parentUI.posX - this.posX)
|
||||||
/** Position of mouse relative to this item */
|
/** Position of mouse relative to this item */
|
||||||
protected val itemRelativeMouseY: Int
|
protected val itemRelativeMouseY: Int
|
||||||
get() = (Terrarum.mouseScreenY - (parentUI.posY) - this.posY)
|
get() = (Terrarum.mouseScreenY - parentUI.posY - this.posY)
|
||||||
|
|
||||||
/** If mouse is hovering over it */
|
/** If mouse is hovering over it */
|
||||||
open val mouseUp: Boolean
|
open val mouseUp: Boolean
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ object JsonFetcher {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forEach(map: JsonValue, action: (String, JsonValue) -> Unit) {
|
/**
|
||||||
|
* Iterates [JsonValue] over its siblings.
|
||||||
|
*
|
||||||
|
* @param map JsonValue to iterate over
|
||||||
|
* @param action A `function(`Name of the sibling or a stringified integer if the `map` is an array`, `JsonValue representation of the sibling`)` -> `Unit`
|
||||||
|
*/
|
||||||
|
fun forEachSiblings(map: JsonValue, action: (String, JsonValue) -> Unit) {
|
||||||
var counter = 0
|
var counter = 0
|
||||||
var entry = map.child
|
var entry = map.child
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
@@ -56,4 +62,17 @@ object JsonFetcher {
|
|||||||
counter += 1
|
counter += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun forEachSiblingsIndexed(map: JsonValue, action: (Int, String, JsonValue) -> Unit) {
|
||||||
|
var counter = 0
|
||||||
|
var entry = map.child
|
||||||
|
while (entry != null) {
|
||||||
|
action(counter, entry.name ?: "$counter", entry)
|
||||||
|
entry = entry.next
|
||||||
|
counter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun JsonValue.forEachSiblings(action: (String, JsonValue) -> Unit) = JsonFetcher.forEachSiblings(this, action)
|
||||||
|
fun JsonValue.forEachSiblingsIndexed(action: (Int, String, JsonValue) -> Unit) = JsonFetcher.forEachSiblingsIndexed(this, action)
|
||||||
Reference in New Issue
Block a user