modular first test

This commit is contained in:
Song Minjae
2017-04-17 16:14:35 +09:00
parent 532c836553
commit f840dbe7de
18 changed files with 128 additions and 56 deletions

View File

@@ -0,0 +1,9 @@
# Load order
# Modules are loaded from top to bottom.
# And yes, you can disable basegame, but we don't recommend.
# Acceptable formats:
# module_name,description_in_English_no_comma,
# module_name,description_in_English_no_comma,(external Jar 1);(external Jar 2); ...
basegame,The base game,
dwarventech,Logic gates and machines,
1 # Load order
2 # Modules are loaded from top to bottom.
3 # And yes, you can disable basegame, but we don't recommend.
4 # Acceptable formats:
5 # module_name,description_in_English_no_comma,
6 # module_name,description_in_English_no_comma,(external Jar 1);(external Jar 2); ...
7 basegame,The base game,
8 dwarventech,Logic gates and machines,

View File

@@ -1,2 +0,0 @@
"id";"filename"
"12345";"tty.lua"
1 id filename
2 12345 tty.lua

View File

@@ -1 +0,0 @@

View File

@@ -1,14 +1,3 @@
Modules may have following subdirectories: ## Register modules
- creatures Only the modules that enlisted in LoadOrder.csv will be loaded.
- factions
- items
- tiles
- weathers
Modules must be packed with Terrarum Virtual Disk format. The packer is located in
lib/TerranVirtualDisk.jar
For more information, please refer to [this link](https://github.com/minjaesong/TerranVirtualDisk)

View File

@@ -5,13 +5,14 @@ import net.torvald.terrarum.weather.toColor
import org.newdawn.slick.Color import org.newdawn.slick.Color
import org.newdawn.slick.Image import org.newdawn.slick.Image
import net.torvald.colourutil.CIEXYZUtil.toColor import net.torvald.colourutil.CIEXYZUtil.toColor
import net.torvald.terrarum.ModuleManager
/** /**
* RGB-modeled CCT calculator * RGB-modeled CCT calculator
* Created by minjaesong on 16-07-26. * Created by minjaesong on 16-07-26.
*/ */
object ColourTemp { object ColourTemp {
private var envOverlayColourmap = Image("./assets/graphics/colourmap/black_body_col_1000_40000_K.tga") private var envOverlayColourmap = Image(ModuleManager.getPath("basegame", "colourmap/black_body_col_1000_40000_K.tga"))
private fun colTempToImagePos(K: Int): Int { private fun colTempToImagePos(K: Int): Int {
if (K < 1000 || K >= 40000) throw IllegalArgumentException("K: out of range. ($K)") if (K < 1000 || K >= 40000) throw IllegalArgumentException("K: out of range. ($K)")

View File

@@ -0,0 +1,77 @@
package net.torvald.terrarum
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import java.io.File
import java.io.FileNotFoundException
import java.nio.file.FileSystems
/**
* Modules Resource Manager
*
* Created by SKYHi14 on 2017-04-17.
*/
object ModuleManager {
data class ModuleMetadata(val order: Int, val isDir: Boolean, val desc: String, val libraries: Array<String>) {
override fun toString() =
"\tModule #$order -- $desc\n" +
"\tExternal libraries: ${libraries.joinToString(", ")}"
}
const val modDir = "./assets/modules"
val moduleInfo = HashMap<String, ModuleMetadata>()
init {
// load modules
val loadOrderCSVparser = CSVParser.parse(
FileSystems.getDefault().getPath("$modDir/LoadOrder.csv").toFile(),
Charsets.UTF_8,
CSVFormat.DEFAULT.withCommentMarker('#')
)
val loadOrder = loadOrderCSVparser.records
loadOrderCSVparser.close()
loadOrder.forEachIndexed { index, it ->
val moduleName = it[0]
println("[ModuleManager] Loading module $moduleName")
val description = it[1]
val libs = it[2].split(';').toTypedArray()
val isDir = FileSystems.getDefault().getPath("$modDir/$moduleName").toFile().isDirectory
moduleInfo[moduleName] = ModuleMetadata(index, isDir, description, libs)
println(moduleInfo[moduleName])
}
}
private fun checkExistence(module: String) {
if (!moduleInfo.containsKey(module))
throw FileNotFoundException("No such module: $module")
}
private fun String.sanitisePath() = if (this[0] == '/' || this[0] == '\\')
this.substring(1..this.lastIndex)
else this
fun getPath(module: String, path: String): String {
checkExistence(module)
return "$modDir/$module/${path.sanitisePath()}"
}
fun getFile(module: String, path: String): File {
checkExistence(module)
return FileSystems.getDefault().getPath(getPath(module, path)).toFile()
}
fun getFiles(module: String, path: String): Array<File> {
checkExistence(module)
val dir = FileSystems.getDefault().getPath(getPath(module, path)).toFile()
if (!dir.isDirectory) {
throw FileNotFoundException("The path is not a directory")
}
else {
return dir.listFiles()
}
}
}

View File

@@ -311,6 +311,11 @@ object Terrarum : StateBasedGame(GAME_NAME) {
} }
} }
// load modules
ModuleManager
gc.graphics.clear() // clean up any 'dust' in the buffer gc.graphics.clear() // clean up any 'dust' in the buffer
//addState(StateVTTest()) //addState(StateVTTest())

View File

@@ -18,9 +18,9 @@ object CreatureBuilder {
* @Param jsonFileName with extension * @Param jsonFileName with extension
*/ */
@Throws(IOException::class, SlickException::class) @Throws(IOException::class, SlickException::class)
operator fun invoke(jsonFileName: String): ActorWithSprite { operator fun invoke(module: String, jsonFileName: String): ActorWithSprite {
val actor = ActorWithSprite(Actor.RenderOrder.MIDDLE) val actor = ActorWithSprite(Actor.RenderOrder.MIDDLE)
InjectCreatureRaw(actor.actorValue, jsonFileName) InjectCreatureRaw(actor.actorValue, module, jsonFileName)
return actor return actor
} }

View File

@@ -5,6 +5,7 @@ import net.torvald.random.Fudge3
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import com.google.gson.JsonObject import com.google.gson.JsonObject
import net.torvald.terrarum.ActorValue import net.torvald.terrarum.ActorValue
import net.torvald.terrarum.ModuleManager
import net.torvald.terrarum.gameactors.ActorHumanoid import net.torvald.terrarum.gameactors.ActorHumanoid
import org.newdawn.slick.SlickException import org.newdawn.slick.SlickException
import java.io.IOException import java.io.IOException
@@ -15,7 +16,6 @@ import java.security.SecureRandom
*/ */
object InjectCreatureRaw { object InjectCreatureRaw {
const val JSONPATH = "./assets/raw/creatures/"
private const val JSONMULT = "mult" // one appears in JSON files private const val JSONMULT = "mult" // one appears in JSON files
/** /**
@@ -24,8 +24,8 @@ object InjectCreatureRaw {
* @param actorValueRef ActorValue object to be injected. * @param actorValueRef ActorValue object to be injected.
* @param jsonFileName with extension * @param jsonFileName with extension
*/ */
operator fun invoke(actorValueRef: ActorValue, jsonFileName: String) { operator fun invoke(actorValueRef: ActorValue, module: String, jsonFileName: String) {
val jsonObj = JsonFetcher(JSONPATH + jsonFileName) val jsonObj = JsonFetcher(ModuleManager.getPath(module, "creatures/$jsonFileName"))
val elementsInt = arrayOf(AVKey.BASEHEIGHT, AVKey.TOOLSIZE, AVKey.ENCUMBRANCE) val elementsInt = arrayOf(AVKey.BASEHEIGHT, AVKey.TOOLSIZE, AVKey.ENCUMBRANCE)
val elementsString = arrayOf(AVKey.RACENAME, AVKey.RACENAMEPLURAL) val elementsString = arrayOf(AVKey.RACENAME, AVKey.RACENAMEPLURAL)

View File

@@ -13,7 +13,7 @@ object PlayerBuilder {
operator fun invoke(): Actor { operator fun invoke(): Actor {
val p: Actor = Player(Terrarum.ingame!!.world.time.currentTimeAsGameDate) val p: Actor = Player(Terrarum.ingame!!.world.time.currentTimeAsGameDate)
InjectCreatureRaw(p.actorValue, "CreatureHuman.json") InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
// attach sprite // attach sprite

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.gameactors package net.torvald.terrarum.gameactors
import net.torvald.spriteanimation.SpriteAnimation import net.torvald.spriteanimation.SpriteAnimation
import net.torvald.terrarum.ModuleManager
import net.torvald.terrarum.gameactors.ActorHumanoid import net.torvald.terrarum.gameactors.ActorHumanoid
import net.torvald.terrarum.gameactors.ai.LuaAIWrapper import net.torvald.terrarum.gameactors.ai.LuaAIWrapper
import net.torvald.terrarum.mapdrawer.FeaturesDrawer import net.torvald.terrarum.mapdrawer.FeaturesDrawer
@@ -15,14 +16,14 @@ object PlayerBuilderCynthia {
val p: HumanoidNPC = HumanoidNPC( val p: HumanoidNPC = HumanoidNPC(
LuaAIWrapper("/net/torvald/terrarum/gameactors/ai/scripts/PokemonNPCAI.lua"), LuaAIWrapper("/net/torvald/terrarum/gameactors/ai/scripts/PokemonNPCAI.lua"),
GameDate(100, 143)) // random value thrown GameDate(100, 143)) // random value thrown
InjectCreatureRaw(p.actorValue, "CreatureHuman.json") InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0 p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
p.actorValue[AVKey.NAME] = "Cynthia" p.actorValue[AVKey.NAME] = "Cynthia"
p.makeNewSprite(26, 42, "assets/graphics/sprites/test_player_2.tga") p.makeNewSprite(26, 42, ModuleManager.getPath("basegame", "sprites/test_player_2.tga"))
p.sprite!!.delay = 200 p.sprite!!.delay = 200
p.sprite!!.setRowsAndFrames(1, 1) p.sprite!!.setRowsAndFrames(1, 1)

View File

@@ -5,6 +5,7 @@ import net.torvald.terrarum.gameactors.faction.Faction
import net.torvald.spriteanimation.SpriteAnimation import net.torvald.spriteanimation.SpriteAnimation
import com.google.gson.JsonObject import com.google.gson.JsonObject
import net.torvald.terrarum.ActorValue import net.torvald.terrarum.ActorValue
import net.torvald.terrarum.ModuleManager
import net.torvald.terrarum.gameactors.ActorHumanoid import net.torvald.terrarum.gameactors.ActorHumanoid
import net.torvald.terrarum.gameactors.faction.FactionFactory import net.torvald.terrarum.gameactors.faction.FactionFactory
import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.itemproperties.ItemCodex
@@ -27,11 +28,11 @@ object PlayerBuilderSigrid {
p.referenceID = 0x51621D // the only constant of this procedural universe p.referenceID = 0x51621D // the only constant of this procedural universe
p.makeNewSprite(28, 51, "assets/graphics/sprites/test_player.tga") p.makeNewSprite(28, 51, ModuleManager.getPath("basegame", "sprites/test_player.tga"))
p.sprite!!.delay = 200 p.sprite!!.delay = 200
p.sprite!!.setRowsAndFrames(1, 1) p.sprite!!.setRowsAndFrames(1, 1)
p.makeNewSpriteGlow(28, 51, "assets/graphics/sprites/test_player_glow.tga") p.makeNewSpriteGlow(28, 51, ModuleManager.getPath("basegame", "sprites/test_player_glow.tga"))
p.spriteGlow!!.delay = 200 p.spriteGlow!!.delay = 200
p.spriteGlow!!.setRowsAndFrames(1, 1) p.spriteGlow!!.setRowsAndFrames(1, 1)
@@ -72,7 +73,7 @@ object PlayerBuilderSigrid {
p.setPosition((4096 * FeaturesDrawer.TILE_SIZE).toDouble(), (300 * 16).toDouble()) p.setPosition((4096 * FeaturesDrawer.TILE_SIZE).toDouble(), (300 * 16).toDouble())
p.faction.add(FactionFactory.create("FactionSigrid.json")) p.faction.add(FactionFactory.create("basegame", "factions/FactionSigrid.json"))

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.gameactors package net.torvald.terrarum.gameactors
import net.torvald.terrarum.ModuleManager
import net.torvald.terrarum.gameactors.ai.LuaAIWrapper import net.torvald.terrarum.gameactors.ai.LuaAIWrapper
import net.torvald.terrarum.mapdrawer.FeaturesDrawer import net.torvald.terrarum.mapdrawer.FeaturesDrawer
@@ -9,14 +10,14 @@ import net.torvald.terrarum.mapdrawer.FeaturesDrawer
object PlayerBuilderTestSubject1 { object PlayerBuilderTestSubject1 {
operator fun invoke(): Player { operator fun invoke(): Player {
val p: Player = Player(GameDate(100, 143)) // random value thrown val p: Player = Player(GameDate(100, 143)) // random value thrown
InjectCreatureRaw(p.actorValue, "CreatureHuman.json") InjectCreatureRaw(p.actorValue, "basegame", "CreatureHuman.json")
p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0 p.actorValue[AVKey.__PLAYER_QUICKSLOTSEL] = 0
p.actorValue[AVKey.NAME] = "Test Subject 1" p.actorValue[AVKey.NAME] = "Test Subject 1"
p.makeNewSprite(48, 52, "assets/graphics/sprites/npc_template_anim_prototype.tga") p.makeNewSprite(48, 52, ModuleManager.getPath("basegame", "sprites/npc_template_anim_prototype.tga"))
p.sprite!!.delay = 200 p.sprite!!.delay = 200
p.sprite!!.setRowsAndFrames(2, 4) p.sprite!!.setRowsAndFrames(2, 4)

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.gameactors.faction
import net.torvald.JsonFetcher import net.torvald.JsonFetcher
import com.google.gson.JsonObject import com.google.gson.JsonObject
import net.torvald.terrarum.ModuleManager
import java.io.IOException import java.io.IOException
@@ -10,14 +11,12 @@ import java.io.IOException
*/ */
object FactionFactory { object FactionFactory {
const val JSONPATH = "./assets/raw/factions/"
/** /**
* @param filename with extension * @param filename with extension
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun create(filename: String): Faction { fun create(module: String, path: String): Faction {
val jsonObj = JsonFetcher(JSONPATH + filename) val jsonObj = JsonFetcher(ModuleManager.getPath(module, path))
val factionObj = Faction(jsonObj.get("factionname").asString) val factionObj = Faction(jsonObj.get("factionname").asString)
jsonObj.get("factionamicable").asJsonArray.forEach { factionObj.addFactionAmicable(it.asString) } jsonObj.get("factionamicable").asJsonArray.forEach { factionObj.addFactionAmicable(it.asString) }

View File

@@ -2,14 +2,11 @@ package net.torvald.terrarum.mapdrawer
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.PairedMapLayer import net.torvald.terrarum.gameworld.PairedMapLayer
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.tileproperties.Tile import net.torvald.terrarum.tileproperties.Tile
import net.torvald.terrarum.tileproperties.TileCodex import net.torvald.terrarum.tileproperties.TileCodex
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.blendAlphaMap import net.torvald.terrarum.*
import net.torvald.terrarum.concurrent.ThreadParallel import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.blendMul
import net.torvald.terrarum.blendNormal
import net.torvald.terrarum.mapdrawer.FeaturesDrawer.TILE_SIZE import net.torvald.terrarum.mapdrawer.FeaturesDrawer.TILE_SIZE
import net.torvald.terrarum.mapdrawer.LightmapRenderer.normaliseToColour import net.torvald.terrarum.mapdrawer.LightmapRenderer.normaliseToColour
import net.torvald.terrarum.mapdrawer.MapCamera.x import net.torvald.terrarum.mapdrawer.MapCamera.x
@@ -28,9 +25,9 @@ object TilesDrawer {
private val TILE_SIZE = FeaturesDrawer.TILE_SIZE private val TILE_SIZE = FeaturesDrawer.TILE_SIZE
private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat() private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat()
var tilesTerrain: SpriteSheet = SpriteSheet("./assets/graphics/terrain/terrain.tga", TILE_SIZE, TILE_SIZE) var tilesTerrain: SpriteSheet = SpriteSheet(ModuleManager.getPath("basegame", "tiles/terrain.tga"), TILE_SIZE, TILE_SIZE)
private set // Slick has some weird quirks with PNG's transparency. I'm using 32-bit targa here. private set // Slick has some weird quirks with PNG's transparency. I'm using 32-bit targa here.
var tilesWire: SpriteSheet = SpriteSheet("./assets/graphics/terrain/wire.tga", TILE_SIZE, TILE_SIZE) var tilesWire: SpriteSheet = SpriteSheet(ModuleManager.getPath("basegame", "tiles/wire.tga"), TILE_SIZE, TILE_SIZE)
private set private set
val WALL = GameWorld.WALL val WALL = GameWorld.WALL
@@ -52,7 +49,7 @@ object TilesDrawer {
* It holds different shading rule to discriminate with group 02, index 0 is single tile. * It holds different shading rule to discriminate with group 02, index 0 is single tile.
* These are the tiles that only connects to itself, will not connect to colour variants * These are the tiles that only connects to itself, will not connect to colour variants
*/ */
val TILES_CONNECT_SELF = arrayOf( var TILES_CONNECT_SELF = arrayOf(
Tile.ICE_MAGICAL, Tile.ICE_MAGICAL,
Tile.GLASS_CRUDE, Tile.GLASS_CRUDE,
Tile.GLASS_CLEAN, Tile.GLASS_CLEAN,
@@ -101,7 +98,7 @@ object TilesDrawer {
* Connectivity group 02 : natural tiles * Connectivity group 02 : natural tiles
* It holds different shading rule to discriminate with group 01, index 0 is middle tile. * It holds different shading rule to discriminate with group 01, index 0 is middle tile.
*/ */
val TILES_CONNECT_MUTUAL = arrayOf( var TILES_CONNECT_MUTUAL = arrayOf(
Tile.STONE, Tile.STONE,
Tile.STONE_QUARRIED, Tile.STONE_QUARRIED,
Tile.STONE_TILE_WHITE, Tile.STONE_TILE_WHITE,
@@ -166,7 +163,7 @@ object TilesDrawer {
/** /**
* Torches, levers, switches, ... * Torches, levers, switches, ...
*/ */
val TILES_WALL_STICKER = arrayOf( var TILES_WALL_STICKER = arrayOf(
Tile.TORCH, Tile.TORCH,
Tile.TORCH_FROST, Tile.TORCH_FROST,
Tile.TORCH_OFF, Tile.TORCH_OFF,
@@ -176,7 +173,7 @@ object TilesDrawer {
/** /**
* platforms, ... * platforms, ...
*/ */
val TILES_WALL_STICKER_CONNECT_SELF = arrayOf( var TILES_WALL_STICKER_CONNECT_SELF = arrayOf(
Tile.PLATFORM_BIRCH, Tile.PLATFORM_BIRCH,
Tile.PLATFORM_BLOODROSE, Tile.PLATFORM_BLOODROSE,
Tile.PLATFORM_EBONY, Tile.PLATFORM_EBONY,
@@ -189,7 +186,7 @@ object TilesDrawer {
* will blend colour using colour multiplication * will blend colour using colour multiplication
* i.e. red hues get lost if you dive into the water * i.e. red hues get lost if you dive into the water
*/ */
val TILES_BLEND_MUL = arrayOf( var TILES_BLEND_MUL = arrayOf(
Tile.WATER, Tile.WATER,
Tile.WATER_1, Tile.WATER_1,
Tile.WATER_2, Tile.WATER_2,

View File

@@ -4,14 +4,11 @@ import com.jme3.math.FastMath
import net.torvald.JsonFetcher import net.torvald.JsonFetcher
import net.torvald.colourutil.* import net.torvald.colourutil.*
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.*
import net.torvald.terrarum.blendMul
import net.torvald.terrarum.blendNormal
import net.torvald.terrarum.gameactors.ParticleTestRain import net.torvald.terrarum.gameactors.ParticleTestRain
import net.torvald.terrarum.gamecontroller.Key import net.torvald.terrarum.gamecontroller.Key
import net.torvald.terrarum.gamecontroller.KeyToggler import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.getPixel
import org.newdawn.slick.Color import org.newdawn.slick.Color
import org.newdawn.slick.GameContainer import org.newdawn.slick.GameContainer
import org.newdawn.slick.Graphics import org.newdawn.slick.Graphics
@@ -47,14 +44,12 @@ object WeatherMixer {
const val WEATHER_GENERIC_RAIN = "genericrain" const val WEATHER_GENERIC_RAIN = "genericrain"
// TODO add weather classification indices manually // TODO add weather classification indices manually
const val RAW_DIR = "./assets/raw/weathers"
init { init {
weatherList = HashMap<String, ArrayList<BaseModularWeather>>() weatherList = HashMap<String, ArrayList<BaseModularWeather>>()
// read weather descriptions from assets/weather (modular weather) // read weather descriptions from assets/weather (modular weather)
val weatherRawValidList = ArrayList<File>() val weatherRawValidList = ArrayList<File>()
val weatherRaws = File(RAW_DIR).listFiles() val weatherRaws = ModuleManager.getFiles("basegame", "weathers")
weatherRaws.forEach { weatherRaws.forEach {
if (!it.isDirectory && it.name.endsWith(".json")) if (!it.isDirectory && it.name.endsWith(".json"))
weatherRawValidList.add(it) weatherRawValidList.add(it)
@@ -191,7 +186,7 @@ object WeatherMixer {
] ]
} }
*/ */
val pathToImage = "./assets/graphics/weathers" val pathToImage = "weathers"
val JSON = JsonFetcher(path) val JSON = JsonFetcher(path)
@@ -212,7 +207,7 @@ object WeatherMixer {
// parse globalLight // parse globalLight
if (globalLightInJson.isString) if (globalLightInJson.isString)
globalLight = Image("$pathToImage/${globalLightInJson.asString}") globalLight = Image(ModuleManager.getPath("basegame", "$pathToImage/${globalLightInJson.asString}"))
else if (globalLightInJson.isNumber) { else if (globalLightInJson.isNumber) {
// make 1x1 image with specified colour // make 1x1 image with specified colour
globalLight = Image(1, 1) globalLight = Image(1, 1)
@@ -224,7 +219,7 @@ object WeatherMixer {
// parse skyboxGradColourMap // parse skyboxGradColourMap
if (skyboxInJson.isString) if (skyboxInJson.isString)
skybox = Image("$pathToImage/${skyboxInJson.asString}") skybox = Image(ModuleManager.getPath("basegame", "$pathToImage/${skyboxInJson.asString}"))
else if (globalLightInJson.isNumber) { else if (globalLightInJson.isNumber) {
// make 1x2 image with specified colour // make 1x2 image with specified colour
skybox = Image(1, 2) skybox = Image(1, 2)
@@ -236,7 +231,7 @@ object WeatherMixer {
// get extra images // get extra images
for (i in extraImagesPath) for (i in extraImagesPath)
extraImages.add(Image("$pathToImage/${i.asString}")) extraImages.add(Image(ModuleManager.getPath("basegame", "$pathToImage/${i.asString}")))
// get mix from // get mix from