asset archiving wip, font update

This commit is contained in:
minjaesong
2026-02-19 09:53:32 +09:00
parent 661d516800
commit 04b49b8a5c
80 changed files with 617 additions and 336 deletions

View File

@@ -0,0 +1,10 @@
wireItemID;const
wire@dwarventech:1;0.995
wire@dwarventech:2;0.995
wire@dwarventech:3;0.995
wire@dwarventech:4;0.995
wire@dwarventech:5;0.995
wire@dwarventech:6;0.999
wire@dwarventech:7;0.999
wire@dwarventech:8;0.999
1 wireItemID const
2 wire@dwarventech:1 0.995
3 wire@dwarventech:2 0.995
4 wire@dwarventech:3 0.995
5 wire@dwarventech:4 0.995
6 wire@dwarventech:5 0.995
7 wire@dwarventech:6 0.999
8 wire@dwarventech:7 0.999
9 wire@dwarventech:8 0.999

View File

@@ -5,6 +5,7 @@ what:
assets:
./make_assets_release.sh || true
./make_assets_archive.sh
linux_x86:
./build_app_linux_x86.sh

View File

@@ -9,8 +9,8 @@ RUNTIME="runtime-linux-arm"
DESKTOPFILE="../out/build_autogen_linux.desktop"
JARNAME="TerrarumBuild.jar"
if [ ! -f "out/assets.tar.zst" ] || [ ! -f "out/assets.manifest" ]; then
echo "'assets.tar.zst' or 'assets.manifest' not found in out/; run 'make assets' first." >&2
if [ ! -f "out/assets.tevd" ]; then
echo "'assets.tevd' not found in out/; run 'make assets' first." >&2
exit 1
fi
@@ -29,9 +29,8 @@ mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
mv $DESTDIR/out/$RUNTIME/bin/java $DESTDIR/out/$RUNTIME/bin/java
# Copy over the asset archive, manifest, and jarfile
cp "out/assets.tar.zst" $DESTDIR/
cp "out/assets.manifest" $DESTDIR/
# Copy over the asset archive and jarfile
cp "out/assets.tevd" $DESTDIR/
cp "../out/$JARNAME" $DESTDIR/out/
# Pack everything to AppImage

View File

@@ -9,8 +9,8 @@ RUNTIME="runtime-linux-x86"
DESKTOPFILE="../out/build_autogen_linux.desktop"
JARNAME="TerrarumBuild.jar"
if [ ! -f "out/assets.tar.zst" ] || [ ! -f "out/assets.manifest" ]; then
echo "'assets.tar.zst' or 'assets.manifest' not found in out/; run 'make assets' first." >&2
if [ ! -f "out/assets.tevd" ]; then
echo "'assets.tevd' not found in out/; run 'make assets' first." >&2
exit 1
fi
@@ -29,9 +29,8 @@ mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
mv $DESTDIR/out/$RUNTIME/bin/java $DESTDIR/out/$RUNTIME/bin/java
# Copy over the asset archive, manifest, and jarfile
cp "out/assets.tar.zst" $DESTDIR/
cp "out/assets.manifest" $DESTDIR/
# Copy over the asset archive and jarfile
cp "out/assets.tevd" $DESTDIR/
cp "../out/$JARNAME" $DESTDIR/out/
# Pack everything to AppImage

View File

@@ -9,8 +9,8 @@ RUNTIME="runtime-osx-arm"
PLISTFILE="../out/build_autogen_macos_Info.plist"
JARNAME="TerrarumBuild.jar"
if [ ! -f "out/assets.tar.zst" ] || [ ! -f "out/assets.manifest" ]; then
echo "'assets.tar.zst' or 'assets.manifest' not found in out/; run 'make assets' first." >&2
if [ ! -f "out/assets.tevd" ]; then
echo "'assets.tevd' not found in out/; run 'make assets' first." >&2
exit 1
fi
@@ -32,9 +32,8 @@ mkdir $DESTDIR/Contents/MacOS/out
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/out/
mv $DESTDIR/Contents/MacOS/out/$RUNTIME/bin/java $DESTDIR/Contents/MacOS/out/$RUNTIME/bin/java
# Copy over the asset archive, manifest, and jarfile
cp "out/assets.tar.zst" $DESTDIR/Contents/MacOS/
cp "out/assets.manifest" $DESTDIR/Contents/MacOS/
# Copy over the asset archive and jarfile
cp "out/assets.tevd" $DESTDIR/Contents/MacOS/
cp "../out/$JARNAME" $DESTDIR/Contents/MacOS/out/
# zip everything

View File

@@ -9,8 +9,8 @@ RUNTIME="runtime-osx-x86"
PLISTFILE="../out/build_autogen_macos_Info.plist"
JARNAME="TerrarumBuild.jar"
if [ ! -f "out/assets.tar.zst" ] || [ ! -f "out/assets.manifest" ]; then
echo "'assets.tar.zst' or 'assets.manifest' not found in out/; run 'make assets' first." >&2
if [ ! -f "out/assets.tevd" ]; then
echo "'assets.tevd' not found in out/; run 'make assets' first." >&2
exit 1
fi
@@ -32,9 +32,8 @@ mkdir $DESTDIR/Contents/MacOS/out
cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/out/
mv $DESTDIR/Contents/MacOS/out/$RUNTIME/bin/java $DESTDIR/Contents/MacOS/out/$RUNTIME/bin/java
# Copy over the asset archive, manifest, and jarfile
cp "out/assets.tar.zst" $DESTDIR/Contents/MacOS/
cp "out/assets.manifest" $DESTDIR/Contents/MacOS/
# Copy over the asset archive and jarfile
cp "out/assets.tevd" $DESTDIR/Contents/MacOS/
cp "../out/$JARNAME" $DESTDIR/Contents/MacOS/out/
# zip everything

View File

@@ -8,8 +8,8 @@ RUNTIME="runtime-windows-x86"
RCFILE="../out/build_autogen_windows.rc"
JARNAME="TerrarumBuild.jar"
if [ ! -f "out/assets.tar.zst" ] || [ ! -f "out/assets.manifest" ]; then
echo "'assets.tar.zst' or 'assets.manifest' not found in out/; run 'make assets' first." >&2
if [ ! -f "out/assets.tevd" ]; then
echo "'assets.tevd' not found in out/; run 'make assets' first." >&2
exit 1
fi
@@ -34,9 +34,8 @@ mkdir $DESTDIR/out
cp -r "../out/$RUNTIME" $DESTDIR/out/
mv $DESTDIR/out/$RUNTIME/bin/java.exe $DESTDIR/out/$RUNTIME/bin/java.exe
# Copy over the asset archive, manifest, and jarfile
cp "out/assets.tar.zst" $DESTDIR/
cp "out/assets.manifest" $DESTDIR/
# Copy over the asset archive and jarfile
cp "out/assets.tevd" $DESTDIR/
cp "../out/$JARNAME" $DESTDIR/out/
# zip everything

View File

@@ -0,0 +1,43 @@
#!/bin/bash
if (( $EUID == 0 )); then echo "The build process is not meant to be run with root privilege, exiting now." >&2; exit 1; fi
cd "${0%/*}"
SRCDIR="../assets_release"
OUTDIR="out"
JARNAME="TerrarumBuild.jar"
TVDJAR="../lib/TerranVirtualDisk.jar"
if [ ! -d "$SRCDIR" ]; then
echo "Error: $SRCDIR does not exist. Run make_assets_release.sh first." >&2
exit 1
fi
if [ ! -f "../out/$JARNAME" ]; then
echo "Error: ../out/$JARNAME not found. Build the project first." >&2
exit 1
fi
if [ ! -f "$TVDJAR" ]; then
echo "Error: $TVDJAR not found." >&2
exit 1
fi
mkdir -p "$OUTDIR"
echo "Creating assets.tevd from $SRCDIR..."
# Build classpath from project JAR and all library JARs
CP="../out/$JARNAME"
for jar in ../lib/*.jar; do
CP="$CP:$jar"
done
java -cp "$CP" net.torvald.terrarum.AssetArchiveBuilderKt "$SRCDIR" "$OUTDIR/assets.tevd"
if [ $? -ne 0 ]; then
echo "Error: Failed to create assets.tevd" >&2
exit 1
fi
echo "Done. Output:"
echo " $OUTDIR/assets.tevd ($(du -h "$OUTDIR/assets.tevd" | cut -f1))"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2017,15 +2017,15 @@ object BTeXParser {
private lateinit var subtitleFont: TerrarumSansBitmap
fun preloadFonts() {
testFont = TerrarumSansBitmap(App.FONT_DIR, shadowAlpha = bodyTextShadowAlpha, textCacheSize = 4096)
partTitleFont = TerrarumSansBitmap(App.FONT_DIR, shadowAlpha = bodyTextShadowAlpha).also {
testFont = TerrarumSansBitmap(shadowAlpha = bodyTextShadowAlpha, textCacheSize = 4096)
partTitleFont = TerrarumSansBitmap(shadowAlpha = bodyTextShadowAlpha).also {
it.interchar = 1
}
titleFont = TerrarumSansBitmap(App.FONT_DIR).also {
titleFont = TerrarumSansBitmap().also {
it.interchar = 1
it.scale = 2
}
subtitleFont = TerrarumSansBitmap(App.FONT_DIR).also {
subtitleFont = TerrarumSansBitmap().also {
it.interchar = 1
}
fontInit = true

View File

@@ -255,10 +255,6 @@ public class App implements ApplicationListener {
public static DebugTimers debugTimers = new DebugTimers();
public static final String FONT_DIR = "assets/graphics/fonts/terrarum-sans-bitmap";
public static Texture[] ditherPatterns = new Texture[4];
// public static ShaderProgram shaderHicolour;
public static ShaderProgram shaderDebugDiff;
@@ -445,6 +441,7 @@ public class App implements ApplicationListener {
// load configs
getDefaultDirectory();
createDirs();
AssetCache.INSTANCE.init();
initialiseConfig();
readConfigJson();
@@ -582,13 +579,13 @@ public class App implements ApplicationListener {
if (!loadOrder.isEmpty()) {
var modname = loadOrder.get(0).get(0);
var textureFile = Gdx.files.internal("assets/mods/"+modname+"/splashback.png");
var textureFile = AssetCache.INSTANCE.getFileHandle("mods/"+modname+"/splashback.png");
if (textureFile.exists()) {
splashBackdrop = new TextureRegion(new Texture(textureFile));
splashTextCol = new Color(0xeeeeeeff);
}
var logoFile = Gdx.files.internal("assets/mods/"+modname+"/splashlogo.png");
var logoFile = AssetCache.INSTANCE.getFileHandle("mods/"+modname+"/splashlogo.png");
if (logoFile.exists()) {
splashScreenLogo = new TextureRegion(new Texture(logoFile));
}
@@ -604,10 +601,10 @@ public class App implements ApplicationListener {
}
if (splashBackdrop == null) {
splashBackdrop = new TextureRegion(new Texture("assets/graphics/background_white.png"));
splashBackdrop = new TextureRegion(new Texture(AssetCache.INSTANCE.getFileHandle("graphics/background_white.png")));
}
if (splashScreenLogo == null) {
splashScreenLogo = new TextureRegion(new Texture("assets/graphics/logo.png"));
splashScreenLogo = new TextureRegion(new Texture(AssetCache.INSTANCE.getFileHandle("graphics/logo.png")));
}
Gdx.graphics.setContinuousRendering(true);
@@ -622,13 +619,13 @@ public class App implements ApplicationListener {
ShaderMgr.INSTANCE.compile(Gdx.files.classpath("shaders/shaders.csv"));
CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_take_a_break.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_distance.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer("haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bap", () -> new MusicContainer("haptic_bap", Gdx.files.internal("./assets/audio/effects/haptic_bap.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer("haptic_bop", Gdx.files.internal("./assets/audio/effects/haptic_bop.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bep", () -> new MusicContainer("haptic_bep", Gdx.files.internal("./assets/audio/effects/haptic_bep.ogg").file(), false, true, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer("haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, true, (AudioBank m) -> { highPrioritySoundPlaying = false; return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(AssetCache.INSTANCE.getFileHandle("graphics/gui/health_take_a_break.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(AssetCache.INSTANCE.getFileHandle("graphics/gui/health_distance.tga")));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer("haptic_bup", AssetCache.INSTANCE.getFileHandle("audio/effects/haptic_bup.ogg"), false, true, null, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bap", () -> new MusicContainer("haptic_bap", AssetCache.INSTANCE.getFileHandle("audio/effects/haptic_bap.ogg"), false, true, null, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer("haptic_bop", AssetCache.INSTANCE.getFileHandle("audio/effects/haptic_bop.ogg"), false, true, null, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bep", () -> new MusicContainer("haptic_bep", AssetCache.INSTANCE.getFileHandle("audio/effects/haptic_bep.ogg"), false, true, null, (AudioBank m) -> { return null; }));
CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer("haptic_bip", AssetCache.INSTANCE.getFileHandle("audio/effects/haptic_bip.ogg"), false, true, null, (AudioBank m) -> { highPrioritySoundPlaying = false; return null; }));
// make loading list
CommonResourcePool.INSTANCE.loadAll();
@@ -669,7 +666,7 @@ public class App implements ApplicationListener {
rendererVendor = Gdx.graphics.getGLVersion().getVendorString();
fontGame = new TerrarumSansBitmap(FONT_DIR, false, false, false,
fontGame = new TerrarumSansBitmap(false, false, false,
false,
256, false, 0.5f, false
);
@@ -1062,6 +1059,8 @@ public class App implements ApplicationListener {
deleteTempfiles();
AssetCache.INSTANCE.dispose();
Toolkit.INSTANCE.dispose();
BlurMgr.INSTANCE.dispose();
@@ -1157,11 +1156,11 @@ public class App implements ApplicationListener {
long t1 = System.nanoTime();
CommonResourcePool.INSTANCE.addToLoadingList("blockmarkings_common", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/blocks/block_markings_common.tga"), 16, 16, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("blockmarkings_common", () -> new TextureRegionPack(AssetCache.INSTANCE.getFileHandle("graphics/blocks/block_markings_common.tga"), 16, 16, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("blockmarking_actor", () -> new BlockMarkerActor());
CommonResourcePool.INSTANCE.addToLoadingList("loading_circle_64", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/gui/loading_circle_64.tga"), 64, 64, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("inline_loading_spinner", () -> new TextureRegionPack(Gdx.files.internal("assets/graphics/gui/inline_loading_spinner.tga"), 20, 20, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("inventory_category", () -> new TextureRegionPack("./assets/graphics/gui/inventory/category.tga", 20, 20, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("loading_circle_64", () -> new TextureRegionPack(AssetCache.INSTANCE.getFileHandle("graphics/gui/loading_circle_64.tga"), 64, 64, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("inline_loading_spinner", () -> new TextureRegionPack(AssetCache.INSTANCE.getFileHandle("graphics/gui/inline_loading_spinner.tga"), 20, 20, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.addToLoadingList("inventory_category", () -> new TextureRegionPack(AssetCache.INSTANCE.getFileHandle("graphics/gui/inventory/category.tga"), 20, 20, 0, 0, 0, 0, false, false, false));
CommonResourcePool.INSTANCE.loadAll();
// shaderHicolour = loadShaderFromClasspath("shaders/default.vert", "shaders/hicolour.frag");
@@ -1226,12 +1225,12 @@ public class App implements ApplicationListener {
else {
environment = RunningEnvironment.PC;
}*/
fontUITitle = new TerrarumSansBitmap(FONT_DIR, false, false, false,
fontUITitle = new TerrarumSansBitmap(false, false, false,
false,
64, false, 0.5f, false
);
fontUITitle.setInterchar(1);
fontGameFBO = new TerrarumSansBitmap(FONT_DIR, false, true, false,
fontGameFBO = new TerrarumSansBitmap(false, true, false,
false,
64, false, 203f/255f, false
);
@@ -1514,6 +1513,8 @@ public class App implements ApplicationListener {
/** defaultDir + "/Custom/Music" */
public static String customMusicDir;
public static String customAmbientDir;
/** defaultDir + "/Caches" */
public static String cachesDir;
private static void getDefaultDirectory() {
String OS = OSName.toUpperCase();
@@ -1551,6 +1552,7 @@ public class App implements ApplicationListener {
customDir = defaultDir + "/Custom";
customMusicDir = customDir + "/Music";
customAmbientDir = customDir + "/Ambient";
cachesDir = defaultDir + "/Caches";
System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion));

View File

@@ -0,0 +1,59 @@
package net.torvald.terrarum
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClusteredFormatDOM
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
import java.io.File
/**
* Build-time tool that creates assets.tevd from assets_release/ directory.
*
* Usage: java -cp <classpath> net.torvald.terrarum.AssetArchiveBuilderKt [assets_release_dir] [output_file]
*/
fun main(args: Array<String>) {
val srcDir = File(if (args.isNotEmpty()) args[0] else "assets_release")
val outFile = File(if (args.size > 1) args[1] else "out/assets.tevd")
if (!srcDir.exists() || !srcDir.isDirectory) {
System.err.println("Error: Source directory '${srcDir.path}' does not exist or is not a directory.")
System.exit(1)
}
outFile.parentFile?.mkdirs()
if (outFile.exists()) outFile.delete()
println("Scanning $srcDir...")
var totalSize = 0L
var fileCount = 0
srcDir.walkTopDown().filter { it.isFile }.forEach {
totalSize += it.length()
fileCount++
}
// Calculate capacity in sectors (4096 bytes per sector/cluster)
// Add overhead for FAT entries and directory structures
val clusterSize = ClusteredFormatDOM.CLUSTER_SIZE
val sectorsForData = (totalSize + clusterSize - 1) / clusterSize
// Add ~25% overhead for FAT, directory entries, and slack space
val capacityInSectors = ((sectorsForData * 1.25) + fileCount + 256).toLong()
.coerceAtMost(ClusteredFormatDOM.MAX_CAPA_IN_SECTORS.toLong())
.toInt()
println(" Files: $fileCount")
println(" Total size: ${totalSize / 1024} KB")
println(" Allocating $capacityInSectors sectors...")
println("Creating archive: ${outFile.path}")
val diskArchive = ClusteredFormatDOM.createNewArchive(outFile, Charsets.UTF_8, "Terrarum Assets", capacityInSectors)
val dom = ClusteredFormatDOM(diskArchive)
println("Importing files from ${srcDir.path}...")
val root = Clustfile(dom, "/")
root.importFrom(srcDir)
println("Trimming archive...")
dom.trimArchive()
dom.dispose()
println("Done. Output:")
println(" ${outFile.path} (${outFile.length() / 1024} KB, $fileCount files)")
}

View File

@@ -0,0 +1,68 @@
package net.torvald.terrarum
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClusteredFormatDOM
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
import java.io.File
import java.io.RandomAccessFile
/**
* Central accessor for the TerranVirtualDisk asset archive.
* In distribution mode, assets are read directly from assets.tevd.
* In development mode, assets are read from the local ./assets/ directory.
*/
object AssetCache {
private val archivePath = File("./assets.tevd")
/** Whether we're running from a distribution archive */
val isDistribution: Boolean get() = archivePath.exists()
private var dom: ClusteredFormatDOM? = null
/**
* Open the archive on startup. Call early, after defaultDir is set.
*/
fun init() {
if (isDistribution) {
println("[AssetCache] Distribution mode: opening ${archivePath.path}")
dom = ClusteredFormatDOM(RandomAccessFile(archivePath, "r"))
println("[AssetCache] Archive opened successfully")
} else {
println("[AssetCache] No archive found, using loose assets (development mode)")
}
}
/**
* Get a Clustfile for a path relative to the assets root.
*/
fun getClustfile(relativePath: String): Clustfile {
val path = if (relativePath.startsWith("/")) relativePath else "/$relativePath"
return Clustfile(dom!!, path)
}
/**
* Get a GDX FileHandle — returns ClustfileHandle in distribution, Gdx.files.internal in dev.
*/
fun getFileHandle(relativePath: String): FileHandle {
return if (isDistribution) ClustfileHandle(getClustfile(relativePath))
else Gdx.files.internal("./assets/$relativePath")
}
/**
* Resolve a path string. In dev mode returns local path; in distribution mode throws
* as callers should use getFileHandle() instead.
*/
fun resolve(relativePath: String): String {
return if (isDistribution) throw UnsupportedOperationException(
"Use AssetCache.getFileHandle(\"$relativePath\") in distribution mode"
)
else "./assets/$relativePath"
}
fun dispose() {
dom?.dispose()
dom = null
}
}

View File

@@ -0,0 +1,64 @@
package net.torvald.terrarum
import com.badlogic.gdx.files.FileHandle
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClustfileInputStream
import java.io.File
import java.io.InputStream
/**
* A GDX FileHandle backed by a Clustfile from TerranVirtualDisk.
* Allows transparent asset loading from .tevd archives using all standard GDX APIs.
*/
class ClustfileHandle(private val clustfile: Clustfile) : FileHandle() {
override fun read(): InputStream = ClustfileInputStream(clustfile)
override fun readBytes(): ByteArray = clustfile.readBytes()
override fun readString(charset: String?): String = String(readBytes(), charset(charset ?: "UTF-8"))
override fun exists(): Boolean = clustfile.exists()
override fun length(): Long = clustfile.length()
override fun isDirectory(): Boolean = clustfile.isDirectory
override fun name(): String = clustfile.name
override fun path(): String = clustfile.path
override fun extension(): String {
val n = name()
val dotIndex = n.lastIndexOf('.')
return if (dotIndex == -1) "" else n.substring(dotIndex + 1)
}
override fun nameWithoutExtension(): String {
val n = name()
val dotIndex = n.lastIndexOf('.')
return if (dotIndex == -1) n else n.substring(0, dotIndex)
}
override fun list(): Array<FileHandle> {
return clustfile.listFiles()?.map { ClustfileHandle(it) }?.toTypedArray() ?: arrayOf()
}
override fun child(name: String): FileHandle {
val childPath = if (clustfile.path.endsWith("/")) "${clustfile.path}$name" else "${clustfile.path}/$name"
return ClustfileHandle(Clustfile(clustfile.DOM, childPath))
}
override fun parent(): FileHandle {
val parentFile = clustfile.parentFile
return if (parentFile != null) ClustfileHandle(parentFile)
else ClustfileHandle(Clustfile(clustfile.DOM, "/"))
}
override fun file(): File {
// Return a dummy File for logging/toString purposes only
return File(clustfile.path)
}
override fun toString(): String = clustfile.path
}

View File

@@ -103,7 +103,7 @@ object ModMgr {
NOT_EVEN_THERE
}
const val modDirInternal = "./assets/mods"
val modDirInternal: String get() = "./assets/mods"
val modDirExternal = "${App.defaultDir}/Modules"
/** Module name (directory name), ModuleMetadata */
@@ -161,21 +161,46 @@ object ModMgr {
try {
val modMetadata = Properties()
val _internalFile = File("$modDirInternal/$moduleName/$metaFilename")
val _externalFile = File("$modDirExternal/$moduleName/$metaFilename")
// external mod has precedence over the internal
val isInternal = if (_externalFile.exists()) false else if (_internalFile.exists()) true else throw FileNotFoundException()
val file = if (isInternal) _internalFile else _externalFile
val internalExists = if (AssetCache.isDistribution)
AssetCache.getFileHandle("mods/$moduleName/$metaFilename").exists()
else
File("$modDirInternal/$moduleName/$metaFilename").exists()
val isInternal = if (_externalFile.exists()) false else if (internalExists) true else throw FileNotFoundException()
val modDir = if (isInternal) modDirInternal else modDirExternal
fun getGdxFile(path: String) = if (isInternal) Gdx.files.internal(path) else Gdx.files.absolute(path)
fun getGdxFileLocal(path: String) = if (isInternal) {
if (AssetCache.isDistribution) AssetCache.getFileHandle(path.removePrefix("./assets/"))
else Gdx.files.internal(path)
} else Gdx.files.absolute(path)
modMetadata.load(FileInputStream(file))
// Load metadata
if (isInternal && AssetCache.isDistribution) {
val metaHandle = AssetCache.getFileHandle("mods/$moduleName/$metaFilename")
modMetadata.load(metaHandle.read())
} else {
val file = if (isInternal) File("$modDirInternal/$moduleName/$metaFilename") else _externalFile
modMetadata.load(FileInputStream(file))
}
if (File("$modDir/$moduleName/$defaultConfigFilename").exists()) {
// Load default config
val defaultConfigHandle = if (isInternal && AssetCache.isDistribution)
AssetCache.getFileHandle("mods/$moduleName/$defaultConfigFilename")
else
null
val defaultConfigExists = if (defaultConfigHandle != null)
defaultConfigHandle.exists()
else
File("$modDir/$moduleName/$defaultConfigFilename").exists()
if (defaultConfigExists) {
try {
val defaultConfig = JsonFetcher("$modDir/$moduleName/$defaultConfigFilename")
val defaultConfig = if (defaultConfigHandle != null)
JsonFetcher.invoke(defaultConfigHandle)
else
JsonFetcher("$modDir/$moduleName/$defaultConfigFilename")
// read config and store it to the game
var entry: JsonValue? = defaultConfig.child
@@ -208,16 +233,29 @@ object ModMgr {
val jar = modMetadata.getProperty("jar")
val jarHash = modMetadata.getProperty("jarhash").uppercase()
val dependency = modMetadata.getProperty("dependency").split(Regex(""";[ ]*""")).filter { it.isNotEmpty() }.toTypedArray()
val isDir = FileSystems.getDefault().getPath("$modDir/$moduleName").toFile().isDirectory
val isDir = if (isInternal && AssetCache.isDistribution)
true // internal mods in archive are always "directories"
else
FileSystems.getDefault().getPath("$modDir/$moduleName").toFile().isDirectory
val configPlan = ArrayList<String>()
File("$modDir/$moduleName/configplan.csv").let {
if (it.exists() && it.isFile) {
configPlan.addAll(it.readLines(Common.CHARSET).filter { it.isNotBlank() })
val configPlanHandle = if (isInternal && AssetCache.isDistribution)
AssetCache.getFileHandle("mods/$moduleName/configplan.csv")
else
null
if (configPlanHandle != null) {
if (configPlanHandle.exists()) {
configPlan.addAll(configPlanHandle.readString("UTF-8").lines().filter { it.isNotBlank() })
}
} else {
File("$modDir/$moduleName/configplan.csv").let {
if (it.exists() && it.isFile) {
configPlan.addAll(it.readLines(Common.CHARSET).filter { it.isNotBlank() })
}
}
}
module = ModuleMetadata(index, isDir, getGdxFile("$modDir/$moduleName/icon.png"), properName, description, descTranslations, author, packageName, entryPoint, releaseDate, version, jar, dependency, isInternal, configPlan)
module = ModuleMetadata(index, isDir, getGdxFileLocal("$modDir/$moduleName/icon.png"), properName, description, descTranslations, author, packageName, entryPoint, releaseDate, version, jar, dependency, isInternal, configPlan)
val versionNumeral = version.split('.')
val versionNumber = versionNumeral.toVersionNumber()
@@ -447,24 +485,36 @@ object ModMgr {
/** Returning files are read-only */
fun getGdxFile(module: String, path: String): FileHandle {
checkExistence(module)
return if (moduleInfo[module]!!.isInternal)
Gdx.files.internal("$modDirInternal/$module/$path")
return if (moduleInfo[module]!!.isInternal) {
if (AssetCache.isDistribution)
ClustfileHandle(AssetCache.getClustfile("mods/$module/$path"))
else
Gdx.files.internal("$modDirInternal/$module/$path")
}
else
Gdx.files.absolute("$modDirExternal/$module/$path")
}
fun getFile(module: String, path: String): File {
// getGdxFile is preferred due to asset archiving
/*fun getFile(module: String, path: String): File {
checkExistence(module)
return if (moduleInfo[module]!!.isInternal)
FileSystems.getDefault().getPath("$modDirInternal/$module/$path").toFile()
return if (moduleInfo[module]!!.isInternal) {
if (AssetCache.isDistribution)
throw UnsupportedOperationException("Use getGdxFile() for internal mod files in distribution mode (module=$module, path=$path)")
else
FileSystems.getDefault().getPath("$modDirInternal/$module/$path").toFile()
}
else
FileSystems.getDefault().getPath("$modDirExternal/$module/$path").toFile()
}
}*/
fun hasFile(module: String, path: String): Boolean {
if (!moduleInfo.containsKey(module)) return false
return getFile(module, path).exists()
return getGdxFile(module, path).exists()
}
fun getFiles(module: String, path: String): Array<File> {
// getGdxFile is preferred due to asset archiving
/*fun getFiles(module: String, path: String): Array<File> {
checkExistence(module)
if (moduleInfo[module]!!.isInternal && AssetCache.isDistribution)
throw UnsupportedOperationException("Use getGdxFiles() for internal mod files in distribution mode (module=$module, path=$path)")
val dir = getFile(module, path)
if (!dir.isDirectory) {
throw FileNotFoundException("The path is not a directory")
@@ -472,7 +522,7 @@ object ModMgr {
else {
return dir.listFiles()
}
}
}*/
fun getGdxFiles(module: String, path: String): Array<FileHandle> {
checkExistence(module)
val dir = getGdxFile(module, path)
@@ -487,20 +537,21 @@ object ModMgr {
/** Get a common file (literal file or directory) from all the installed mods. Files are guaranteed to exist. If a mod does not
* contain the file, the mod will be skipped.
*
* @return List of pairs<modname, file>
* @return List of pairs<modname, filehandle>
*/
fun getFilesFromEveryMod(path: String): List<Pair<String, File>> {
// getGdxFile is preferred due to asset archiving
/*fun getFilesFromEveryMod(path: String): List<Pair<String, FileHandle>> {
val path = path.sanitisePath()
val moduleNames = moduleInfo.keys.toList()
val filesList = ArrayList<Pair<String, File>>()
val filesList = ArrayList<Pair<String, FileHandle>>()
moduleNames.forEach {
val file = getFile(it, path)
val file = getGdxFile(it, path)
if (file.exists()) filesList.add(it to file)
}
return filesList.toList()
}
}*/
/** Get a common file (literal file or directory) from all the installed mods. Files are guaranteed to exist. If a mod does not
* contain the file, the mod will be skipped.
@@ -739,7 +790,7 @@ object ModMgr {
const val langPath = "locales/"
@JvmStatic operator fun invoke(module: String) {
Lang.load(getFile(module, langPath))
Lang.load(getGdxFile(module, langPath))
}
}
@@ -747,25 +798,25 @@ object ModMgr {
const val keebPath = "keylayout/"
@JvmStatic operator fun invoke(module: String) {
val FILE = getFile(module, keebPath)
val DIR = getGdxFile(module, keebPath)
FILE.listFiles { file, s -> s.endsWith(".${IME.KEYLAYOUT_EXTENSION}") }.sortedBy { it.name }.forEach {
printdbg(this, "Registering Low layer ${it.nameWithoutExtension.lowercase()}")
IME.registerLowLayer(it.nameWithoutExtension.lowercase(), IME.parseKeylayoutFile(it))
DIR.list().filter { it.extension().equals(IME.KEYLAYOUT_EXTENSION, ignoreCase = true) }.sortedBy { it.name() }.forEach {
printdbg(this, "Registering Low layer ${it.nameWithoutExtension().lowercase()}")
IME.registerLowLayer(it.nameWithoutExtension().lowercase(), IME.parseKeylayoutFile(it))
}
FILE.listFiles { file, s -> s.endsWith(".${IME.IME_EXTENSION}") }.sortedBy { it.name }.forEach {
printdbg(this, "Registering High layer ${it.nameWithoutExtension.lowercase()}")
IME.registerHighLayer(it.nameWithoutExtension.lowercase(), IME.parseImeFile(it))
DIR.list().filter { it.extension().equals(IME.IME_EXTENSION, ignoreCase = true) }.sortedBy { it.name() }.forEach {
printdbg(this, "Registering High layer ${it.nameWithoutExtension().lowercase()}")
IME.registerHighLayer(it.nameWithoutExtension().lowercase(), IME.parseImeFile(it))
}
val iconFile = getFile(module, keebPath + "icons.tga").let {
if (it.exists()) it else getFile(module, keebPath + "icons.png")
val iconFile = getGdxFile(module, keebPath + "icons.tga").let {
if (it.exists()) it else getGdxFile(module, keebPath + "icons.png")
}
if (iconFile.exists()) {
val iconSheet = TextureRegionPack(iconFile.path, 20, 20)
val iconPixmap = Pixmap(Gdx.files.absolute(iconFile.path))
val iconSheet = TextureRegionPack(iconFile, 20, 20)
val iconPixmap = Pixmap(iconFile)
for (k in 0 until iconPixmap.height step 20) {
val langCode = StringBuilder()
for (c in 0 until 20) {
@@ -855,7 +906,7 @@ object ModMgr {
}
@JvmStatic operator fun invoke(module: String) {
getFiles(module, weatherPath).filter { it.isFile && it.name.lowercase().endsWith(".json") }.forEach {
getGdxFile(module, weatherPath).list().filter { !it.isDirectory && it.name().lowercase().endsWith(".json") }.forEach {
Terrarum.weatherCodex.readFromJson(module, it)
}
}
@@ -878,32 +929,21 @@ object ModMgr {
}
@JvmStatic operator fun invoke(module: String) {
val targetModNames = getFiles(module, retexturesPath).filter { it.isDirectory }
val targetModNames = getGdxFile(module, retexturesPath).list().filter { it.isDirectory }
targetModNames.forEach { baseTargetModDir ->
// modules/<module>/retextures/basegame
// printdbg(this, "baseTargetModDir = $baseTargetModDir")
retexables.forEach { category ->
val dir = File(baseTargetModDir, category)
// modules/<module>/retextures/basegame/blocks
// printdbg(this, "cats: ${dir.path}")
val dir = baseTargetModDir.child(category)
if (dir.isDirectory && dir.exists()) {
dir.listFiles { it: File ->
it.name.contains('-')
}?.forEach {
// <other modname>-<hopefully a number>.tga or .png
val tokens = it.name.split('-')
dir.list().filter { it.name().contains('-') }.forEach {
val tokens = it.name().split('-')
if (tokens.size > 1) {
val modname = tokens[0]
val filename = tokens.tail().joinToString("-")
altFilePaths["$modDirInternal/$modname/$category/$filename"] = getGdxFile(module, "$retexturesPath${baseTargetModDir.name}/$category/${it.name}")
altFilePaths["$modDirInternal/$modname/$category/$filename"] = getGdxFile(module, "$retexturesPath${baseTargetModDir.name()}/$category/${it.name()}")
}
}
}
// retexableCallbacks[category]?.invoke()
}
}
@@ -917,12 +957,12 @@ object ModMgr {
const val smeltingPath = "smelting/"
@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, jsonFile.name)
getGdxFile(module, recipePath).list().filter { !it.isDirectory && it.name().lowercase().endsWith(".json") }.forEach { jsonHandle ->
Terrarum.craftingCodex.addFromJson(JsonFetcher.invoke(jsonHandle), module, jsonHandle.name())
}
getFile(module, smeltingPath).listFiles { it: File -> it.name.lowercase().endsWith(".json") }?.forEach { jsonFile ->
Terrarum.craftingCodex.addSmeltingFromJson(JsonFetcher(jsonFile), module, jsonFile.name)
getGdxFile(module, smeltingPath).list().filter { !it.isDirectory && it.name().lowercase().endsWith(".json") }.forEach { jsonHandle ->
Terrarum.craftingCodex.addSmeltingFromJson(JsonFetcher.invoke(jsonHandle), module, jsonHandle.name())
}
}
}

View File

@@ -1,13 +1,13 @@
package net.torvald.terrarum.audio
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.jme3.math.FastMath
import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.serialise.toUint
import java.io.File
/**
* Created by minjaesong on 2024-01-24.
@@ -26,7 +26,7 @@ object AudioHelper {
return fft
}
else {
val ir = ModMgr.getFile(module, path)
val ir = ModMgr.getGdxFile(module, path)
val fft = createIR(ir)
CommonResourcePool.addToLoadingList(id) { fft }
@@ -36,19 +36,20 @@ object AudioHelper {
}
}
private fun createIR(ir: File): Array<ComplexArray> {
private fun createIR(ir: FileHandle): Array<ComplexArray> {
if (!ir.exists()) {
throw IllegalArgumentException("Impulse Response file '${ir.path}' does not exist.")
throw IllegalArgumentException("Impulse Response file '${ir.path()}' does not exist.")
}
val sampleCount = (ir.length().toInt() / 8)//.coerceAtMost(65536)
val irBytes = ir.readBytes()
val sampleCount = (irBytes.size / 8)//.coerceAtMost(65536)
val fftLen = FastMath.nextPowerOfTwo(sampleCount)
printdbg(this, "IR '${ir.path}' Sample Count = $sampleCount; FFT Length = $fftLen")
printdbg(this, "IR '${ir.path()}' Sample Count = $sampleCount; FFT Length = $fftLen")
val conv = Array(2) { FloatArray(fftLen) }
ir.inputStream().let {
java.io.ByteArrayInputStream(irBytes).let {
for (i in 0 until sampleCount) {
val f1 = Float.fromBits(it.read().and(255) or
it.read().and(255).shl(8) or
@@ -79,7 +80,7 @@ object AudioHelper {
return CommonResourcePool.getAs<Array<FloatArray>>(id)
}
else {
val file = ModMgr.getFile(module, path)
val file = ModMgr.getGdxFile(module, path)
val samples = createAudioInSamples(file)
CommonResourcePool.addToLoadingList(id) { samples }
@@ -89,8 +90,8 @@ object AudioHelper {
}
}
private fun createAudioInSamples(static: File): Array<FloatArray> {
val music = Gdx.audio.newMusic(Gdx.files.absolute(static.absolutePath))
private fun createAudioInSamples(static: FileHandle): Array<FloatArray> {
val music = Gdx.audio.newMusic(static)
val readbuf = ByteArray(AudioProcessBuf.MP3_CHUNK_SIZE * 4)
val OUTBUF_BLOCK_SIZE_IN_BYTES = (48000 * 60) * 2 * 2
var outbuf = ByteArray(OUTBUF_BLOCK_SIZE_IN_BYTES)

View File

@@ -18,13 +18,15 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RAT
import net.torvald.terrarum.serialise.toUint
import net.torvald.unsafe.UnsafeHelper
import net.torvald.unsafe.UnsafePtr
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import javax.sound.sampled.AudioSystem
class MusicContainer(
override val name: String,
val file: File,
val file: File?,
val fileHandle: FileHandle?,
val looping: Boolean = false,
val toRAM: Boolean = false,
val samplingRateOverride: Float?, // this is FIXED sampling rate
@@ -34,6 +36,15 @@ class MusicContainer(
override var channels: Int
val codec: String
// File-based constructors (existing API)
constructor(
name: String,
file: File,
looping: Boolean = false,
toRAM: Boolean = false,
samplingRateOverride: Float?,
songFinishedHook: (AudioBank) -> Unit = {}
) : this(name, file, null, looping, toRAM, samplingRateOverride, songFinishedHook)
// make Java code shorter
constructor(
name: String,
@@ -41,27 +52,37 @@ class MusicContainer(
looping: Boolean = false,
toRAM: Boolean = false,
songFinishedHook: (AudioBank) -> Unit = {}
) : this(name, file, looping, toRAM, null, songFinishedHook)
) : this(name, file, null, looping, toRAM, null, songFinishedHook)
// make Java code shorter
constructor(
name: String,
file: File,
looping: Boolean = false,
songFinishedHook: (AudioBank) -> Unit = {}
) : this(name, file, looping, false, null, songFinishedHook)
) : this(name, file, null, looping, false, null, songFinishedHook)
// make Java code shorter
constructor(
name: String,
file: File,
songFinishedHook: (AudioBank) -> Unit = {}
) : this(name, file, false, false, null, songFinishedHook)
) : this(name, file, null, false, false, null, songFinishedHook)
// FileHandle-based constructor (for TEVD archive support)
constructor(
name: String,
fileHandle: FileHandle,
looping: Boolean = false,
toRAM: Boolean = false,
samplingRateOverride: Float? = null,
songFinishedHook: (AudioBank) -> Unit = {}
) : this(name, null, fileHandle, looping, toRAM, samplingRateOverride, songFinishedHook)
var samplesReadCount = 0L; internal set
override var totalSizeInSamples: Long
private val totalSizeInBytes: Long
private val gdxMusic: Music = Gdx.audio.newMusic(FileHandle(file))
private val gdxMusic: Music = if (file != null) Gdx.audio.newMusic(FileHandle(file)) else Gdx.audio.newMusic(fileHandle!!)
private var soundBuf: UnsafePtr? = null; private set
@@ -94,7 +115,10 @@ class MusicContainer(
rate.toFloat()
}
is Mp3.Music -> {
val tempMusic = Gdx.audio.newMusic(Gdx.files.absolute(file.absolutePath))
val tempMusic = if (file == null)
Gdx.audio.newMusic(fileHandle)
else
Gdx.audio.newMusic(Gdx.files.absolute(file.absolutePath))
val bitstream = tempMusic.extortField<Bitstream>("bitstream")!!
val header = bitstream.readFrame()
val rate = header.sampleRate
@@ -118,11 +142,22 @@ class MusicContainer(
if (it.last() == "Music") it.dropLast(1).last() else it.last()
}
totalSizeInSamples = when (gdxMusic) {
is Wav.Music -> getWavFileSampleCount(file)
is Ogg.Music -> getOggFileSampleCount(file)
is Mp3.Music -> getMp3FileSampleCount(file)
else -> Long.MAX_VALUE
totalSizeInSamples = if (file != null) {
when (gdxMusic) {
is Wav.Music -> getWavFileSampleCount(file)
is Ogg.Music -> getOggFileSampleCount(file)
is Mp3.Music -> getMp3FileSampleCount(file)
else -> Long.MAX_VALUE
}
} else if (fileHandle != null) {
when (gdxMusic) {
is Wav.Music -> getWavFileSampleCountFromHandle(fileHandle)
is Ogg.Music -> getOggFileSampleCountFromHandle(fileHandle)
is Mp3.Music -> getMp3FileSampleCountFromHandle(fileHandle)
else -> Long.MAX_VALUE
}
} else {
Long.MAX_VALUE
}
totalSizeInBytes = totalSizeInSamples * 2 * channels
@@ -329,10 +364,65 @@ class MusicContainer(
}
}
override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
private fun getWavFileSampleCountFromHandle(fh: FileHandle): Long {
return try {
val ais = AudioSystem.getAudioInputStream(BufferedInputStream(fh.read()))
val r = ais.frameLength
ais.close()
r
}
catch (_: Throwable) {
Long.MAX_VALUE
}
}
override fun equals(other: Any?) = this.file.path == (other as MusicContainer).file.path
fun equalInstance(other: Any?) = this.file.path == (other as MusicContainer).file.path && this.hash == (other as MusicContainer).hash
private fun getOggFileSampleCountFromHandle(fh: FileHandle): Long {
return try {
// VorbisFile requires a file path; use a temp file
val tempFile = java.io.File.createTempFile("terrarum_ogg_", ".ogg")
tempFile.deleteOnExit()
tempFile.writeBytes(fh.readBytes())
val vorbisFile = VorbisFile(tempFile.absolutePath)
val r = vorbisFile.pcm_total(0)
tempFile.delete()
r
}
catch (_: Throwable) {
Long.MAX_VALUE
}
}
private fun getMp3FileSampleCountFromHandle(fh: FileHandle): Long {
return try {
val input = BufferedInputStream(fh.read())
val bs = Bitstream(input)
var header = bs.readFrame()
val rate = header.frequency()
var totalSamples = 0L
while (header != null) {
totalSamples += (header.ms_per_frame() * rate / 1000).toLong()
bs.closeFrame()
header = bs.readFrame()
}
bs.close()
input.close()
totalSamples
}
catch (_: Throwable) {
Long.MAX_VALUE
}
}
private val identPath: String get() = file?.path ?: fileHandle?.path() ?: name
override fun toString() = if (name.isEmpty()) (file?.nameWithoutExtension ?: fileHandle?.nameWithoutExtension() ?: "") else name
override fun equals(other: Any?) = this.identPath == (other as MusicContainer).identPath
fun equalInstance(other: Any?) = this.identPath == (other as MusicContainer).identPath && this.hash == (other as MusicContainer).hash
override fun dispose() {
gdxMusic.dispose()
@@ -340,7 +430,10 @@ class MusicContainer(
}
override fun makeCopy(): AudioBank {
val new = MusicContainer(name, file, looping, false, samplingRateOverride, songFinishedHook)
val new = if (file != null)
MusicContainer(name, file, looping, false, samplingRateOverride, songFinishedHook)
else
MusicContainer(name, fileHandle!!, looping, false, samplingRateOverride, songFinishedHook)
synchronized(this) {
if (this.toRAM) {

View File

@@ -38,13 +38,13 @@ object CommandDict {
((listOf("$" to "net.torvald.terrarum")) + ModMgr.loadOrder.reversed().map { it to ModMgr.moduleInfo[it]?.packageName }).forEach { (modName, packageRoot) ->
if (modName == "$" || modName != "$" && ModMgr.hasFile(modName, "commands.csv")) {
val commandsList = if (modName == "$") engineCommandList else ModMgr.getFile(modName, "commands.csv").readLines()
val commandsList = if (modName == "$") engineCommandList else ModMgr.getGdxFile(modName, "commands.csv").readString("UTF-8").lines()
val packageConsole = "$packageRoot.console"
printdbg(this, "Loading console commands from '${packageConsole}'")
// printdbg(this, commandsList.joinToString())
commandsList.forEach { commandName ->
commandsList.filter { it.isNotBlank() }.forEach { commandName ->
val canonicalName = "$packageConsole.$commandName"
val it = ModMgr.moduleClassloader[modName].let {
if (it != null)

View File

@@ -15,7 +15,7 @@ object FactionFactory {
*/
@Throws(IOException::class)
fun create(module: String, path: String): Faction {
val jsonObj = JsonFetcher(ModMgr.getFile(module, path))
val jsonObj = JsonFetcher(ModMgr.getGdxFile(module, path))
val factionObj = Faction(jsonObj.getString("factionname"))
jsonObj.get("factionamicable").asStringArray().forEach { factionObj.addFactionAmicable(it) }

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.gamecontroller
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.App
@@ -173,8 +174,13 @@ object IME {
else -> throw IllegalArgumentException("Unknown candidates mode: $this")
}
fun parseKeylayoutFile(file: File): TerrarumKeyLayout {
val src = file.readText(Charsets.UTF_8)
fun parseKeylayoutFile(fileHandle: FileHandle): TerrarumKeyLayout =
parseKeylayoutFromString(fileHandle.readString("UTF-8"))
fun parseKeylayoutFile(file: File): TerrarumKeyLayout =
parseKeylayoutFromString(file.readText(Charsets.UTF_8))
private fun parseKeylayoutFromString(src: String): TerrarumKeyLayout {
val jsval = context.eval("js", "'use strict';Object.freeze($src)")
val name = jsval.getMember("n").asString()
val capsmode = jsval.getMember("capslock").asString().toCapsMode()
@@ -201,8 +207,6 @@ object IME {
}
}
// println("[IME] Test Keymap print for $name:"); for (keycode in 0 until 256) { print("$keycode:\t"); println(out[keycode].joinToString("\t")) }
return TerrarumKeyLayout(name, capsmode, out, physicalLayout)
}
@@ -215,8 +219,13 @@ object IME {
else -> throw IllegalArgumentException("Unknown operation mode: $this")
}
fun parseImeFile(file: File): TerrarumIME {
val code = file.readText(Charsets.UTF_8)
fun parseImeFile(fileHandle: FileHandle): TerrarumIME =
parseImeFromString(fileHandle.readString("UTF-8"))
fun parseImeFile(file: File): TerrarumIME =
parseImeFromString(file.readText(Charsets.UTF_8))
private fun parseImeFromString(code: String): TerrarumIME {
val jsval = context.eval("js", "\"use strict\";(function(){$code})()")
val name = jsval.getMember("n").asString()
val candidatesCount = jsval.getMember("v").asString().toViewCount()

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.langpack
import com.badlogic.gdx.files.FileHandle
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.tail
@@ -82,47 +83,62 @@ object Lang {
}
}
fun load(localesHandle: FileHandle) {
printdbg(this, "Loading languages from ${localesHandle.path()}")
localesHandle.list().filter { it.isDirectory }.forEach { languageList.add(it.name()) }
// temporary filter
languageList.remove("jaJPysi")
for (lang in languageList) {
printdbg(this, "Loading langpack from ${localesHandle.path()}/$lang/")
val langFiles = localesHandle.child(lang).list()
langFiles.forEach {
if (!it.name().startsWith("Polyglot") && it.name().endsWith(".json")) {
processRegularLangfile(it, lang)
}
else if (it.name().startsWith("Polyglot") && it.name().endsWith(".json")) {
processPolyglotLangFile(it, lang)
}
}
}
}
private fun processRegularLangfile(file: File, lang: String) {
val json = JsonFetcher(file)
/*
* Terrarum langpack JSON structure is:
*
* (root object)
* "<<STRING ID>>" = "<<LOCALISED TEXT>>"
*/
//println(json.entrySet())
JsonFetcher.forEachSiblings(json) { key, value ->
langpack.put("${key}_$lang", value.asString().trim())
}
}
private fun processRegularLangfile(fileHandle: FileHandle, lang: String) {
val json = JsonFetcher(fileHandle)
JsonFetcher.forEachSiblings(json) { key, value ->
langpack.put("${key}_$lang", value.asString().trim())
}
}
private fun processPolyglotLangFile(file: File, lang: String) {
val json = JsonFetcher(file)
/*
* Polyglot JSON structure is:
*
* (root object)
* "resources": object
* "polyglot": object
* (polyglot meta)
* "data": array
* [0]: object
* n = "CONTEXT_CHARACTER_CLASS"
* s = "Class"
* [1]: object
* n = "CONTEXT_CHARACTER_DELETE"
* s = "Delecte Character"
* (the array continues)
*
*/
JsonFetcher.forEachSiblings(json.get("resources").get("data")) { _, entry ->
langpack.put(
"${entry.getString("n")}_$lang",
entry.getString("s").trim()
)
}
}
private fun processPolyglotLangFile(fileHandle: FileHandle, lang: String) {
val json = JsonFetcher(fileHandle)
JsonFetcher.forEachSiblings(json.get("resources").get("data")) { _, entry ->
langpack.put(
"${entry.getString("n")}_$lang",
entry.getString("s").trim()
)
}
}
private val bindOp = ">>="

View File

@@ -161,10 +161,10 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
try {
val file = ModMgr.getFile("basegame", "demoworld")
val reader = java.io.FileReader(file)
val fileHandle = ModMgr.getGdxFile("basegame", "demoworld")
val reader = fileHandle.reader("UTF-8")
//ReadWorld.readWorldAndSetNewWorld(Terrarum.ingame!! as TerrarumIngame, reader)
val world = ReadSimpleWorld(reader, file)
val world = ReadSimpleWorld(reader, fileHandle.file())
demoWorld = world
demoWorld.worldTime.timeDelta = 30
printdbg(this, "Demo world loaded")

View File

@@ -46,7 +46,7 @@ object InstrumentLoader {
CommonResourcePool.getAs<Pair<FloatArray, FloatArray>>("${baseResourceName}_$it")
}
val masterFile = MusicContainer("${idBase}_${initialNote}", ModMgr.getFile(module, path))
val masterFile = MusicContainer("${idBase}_${initialNote}", ModMgr.getGdxFile(module, path))
val masterSamplesL = FloatArray(masterFile.totalSizeInSamples.toInt())
val masterSamplesR = FloatArray(masterFile.totalSizeInSamples.toInt())
masterFile.readSamples(masterSamplesL, masterSamplesR)

View File

@@ -23,7 +23,7 @@ open class ActorLobbed(throwPitch: Float) : ActorWithBody() {
@Transient private val pitch = throwPitch.coerceIn(0.5f, 2f)
@Transient private val whooshSound = MusicContainer(
"throw_low_short", ModMgr.getFile("basegame", "audio/effects/throwing/throw_low_short.wav"),
"throw_low_short", ModMgr.getGdxFile("basegame", "audio/effects/throwing/throw_low_short.wav"),
toRAM = true,
samplingRateOverride = 48000f * pitch
)
@@ -63,17 +63,17 @@ open class ActorPrimedBomb(
private var explosionCalled = false
@Transient private val boomSound = MusicContainer(
"boom", ModMgr.getFile("basegame", "audio/effects/explosion/bang_bomb.wav"), toRAM = true
"boom", ModMgr.getGdxFile("basegame", "audio/effects/explosion/bang_bomb.wav"), toRAM = true
) {
this.flagDespawn()
}
@Transient private val fuseSound = MusicContainer(
"fuse", ModMgr.getFile("basegame", "audio/effects/explosion/fuse.wav"), toRAM = true
"fuse", ModMgr.getGdxFile("basegame", "audio/effects/explosion/fuse.wav"), toRAM = true
) {
this.flagDespawn()
}
@Transient private val fuseSoundCont = MusicContainer(
"fuse_continue", ModMgr.getFile("basegame", "audio/effects/explosion/fuse_continue.wav"), toRAM = true
"fuse_continue", ModMgr.getGdxFile("basegame", "audio/effects/explosion/fuse_continue.wav"), toRAM = true
) {
this.flagDespawn()
}

View File

@@ -171,7 +171,7 @@ class FixtureAlloyingFurnace : FixtureBase {
}
@Transient val static = MusicContainer("bonfire", ModMgr.getFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient val static = MusicContainer("bonfire", ModMgr.getGdxFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient val light = Cvec(0.5f, 0.18f, 0f, 0f)
@Transient override var lightBoxList = arrayListOf(Lightbox(Hitbox(0.0, 0.0, TILE_SIZED * 2, TILE_SIZED * 2), light))

View File

@@ -57,7 +57,7 @@ class FixtureFurnaceAndAnvil : FixtureBase, CraftingStation {
}
}
@Transient val static = MusicContainer("bonfire", ModMgr.getFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient val static = MusicContainer("bonfire", ModMgr.getGdxFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient override var lightBoxList = arrayListOf(Lightbox(Hitbox(0.0, 0.0, TerrarumAppConfiguration.TILE_SIZED * 2, TerrarumAppConfiguration.TILE_SIZED * 2), Cvec(0.5f, 0.18f, 0f, 0f)))

View File

@@ -156,7 +156,7 @@ class FixtureSmelterBasic : FixtureBase {
this.mainUI = UISmelterBasic(this)
}
@Transient val static = MusicContainer("bonfire", ModMgr.getFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient val static = MusicContainer("bonfire", ModMgr.getGdxFile("basegame", "audio/effects/static/bonfire.ogg"), true)
@Transient val light = Cvec(0.5f, 0.18f, 0f, 0f)
@Transient override var lightBoxList = arrayListOf(Lightbox(Hitbox(0.0, 2*TILE_SIZED, TILE_SIZED * 2, TILE_SIZED * 2), light))

View File

@@ -98,7 +98,7 @@ class FixtureTypewriter : FixtureBase {
internal class TestLeafletPrimaryUseHandler : FileRefItemPrimaryUseHandler {
override fun use(item: ItemFileRef): Long {
println(item.getAsFile().readText(Common.CHARSET))
println(item.getAsGdxFile().readString(Common.CHARSET.displayName()))
return 0L
}
}

View File

@@ -24,7 +24,7 @@ object InjectCreatureRaw {
* @param jsonFileName with extension
*/
operator fun invoke(actorValueRef: ActorValue, module: String, jsonFileName: String) {
val jsonObj = JsonFetcher(ModMgr.getFile(module, "creatures/$jsonFileName"))
val jsonObj = JsonFetcher(ModMgr.getGdxFile(module, "creatures/$jsonFileName"))
JsonFetcher.forEachSiblings(jsonObj) { key, value -> if (!key.startsWith("_")) {

View File

@@ -7,6 +7,7 @@ import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.gameitems.GameItem
import net.torvald.terrarum.gameitems.ItemID
import com.badlogic.gdx.files.FileHandle
import java.io.File
import java.util.UUID
@@ -42,7 +43,7 @@ open class ItemFileRef(originalID: ItemID) : GameItem(originalID) {
*/
open var refIsShared: Boolean = false
@Transient open lateinit var ref: File
@Transient open lateinit var ref: FileHandle
/**
* Application-defined.
@@ -101,10 +102,10 @@ open class ItemFileRef(originalID: ItemID) : GameItem(originalID) {
else
ModMgr.getGdxFile(refModuleName, refPath)
fun getAsFile() = if (refIsShared)
/*fun getAsFile() = if (refIsShared)
File(App.saveSharedDir + "/$refPath")
else
ModMgr.getFile(refModuleName, refPath)
ModMgr.getFile(refModuleName, refPath)*/
@Transient private var classCache: FileRefItemPrimaryUseHandler? = null

View File

@@ -22,7 +22,7 @@ data class MusicDiscMetadata(val title: String, val author: String, val album: S
object MusicDiscHelper {
fun getMetadata(musicFile: FileHandle): MusicDiscMetadata {
val musicdbFile = musicFile.sibling("_musicdb.json")
val musicdb = JsonFetcher.invoke(musicdbFile.file())
val musicdb = JsonFetcher.invoke(musicdbFile)
val propForThisFile = musicdb.get(musicFile.name())
val artist = propForThisFile.get("artist").asString()
@@ -37,7 +37,7 @@ open class MusicDiscPrototype(originalID: ItemID, module: String, path: String)
override var refPath = path
override var refModuleName = module
override val canBeDynamic = false
@Transient override var ref = ModMgr.getFile(refModuleName, refPath)
@Transient override var ref = ModMgr.getGdxFile(refModuleName, refPath)
override var mediumIdentifier = "music_disc"
init {

View File

@@ -273,7 +273,7 @@ object PickaxeCore : TooltipListener() {
private val soundCue = MusicContainer(
"pickaxe_sound_cue",
ModMgr.getFile("basegame", "audio/effects/accessibility/pickaxe_valuable.ogg"),
ModMgr.getGdxFile("basegame", "audio/effects/accessibility/pickaxe_valuable.ogg"),
toRAM = false
).also {
App.disposables.add(it)

View File

@@ -30,7 +30,7 @@ class UIElemTest : ApplicationAdapter() {
private lateinit var ui: UICanvas
override fun create() {
App.fontGame = TerrarumSansBitmap(App.FONT_DIR, false, true, false,
App.fontGame = TerrarumSansBitmap(false, true, false,
false,
256, false, 0.5f, false
)

View File

@@ -40,7 +40,10 @@ object CSVFetcher {
return csvRecordList
}
fun readFromModule(module: String, path: String) = net.torvald.terrarum.utils.CSVFetcher.readFromFile(ModMgr.getGdxFile(module, path).path())
fun readFromModule(module: String, path: String): List<org.apache.commons.csv.CSVRecord> {
val content = ModMgr.getGdxFile(module, path).readString("UTF-8")
return readFromString(content)
}
fun readFromString(csv: String): List<org.apache.commons.csv.CSVRecord> {
val preprocessed = preprocessCSV(csv)

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.utils
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.JsonReader
import com.badlogic.gdx.utils.JsonValue
import net.torvald.terrarum.App.printdbg
@@ -36,6 +37,13 @@ object JsonFetcher {
return JsonReader().parse(jsonString.toString())
}
@Throws(java.io.IOException::class)
operator fun invoke(fileHandle: FileHandle): JsonValue {
val content = fileHandle.readString("UTF-8")
printdbg(this, "Reading JSON ${fileHandle.path()}")
return JsonReader().parse(content)
}
fun readFromJsonString(stringReader: Reader): JsonValue {
return JsonReader().parse(stringReader.readText())
}

View File

@@ -1,5 +1,6 @@
package net.torvald.terrarum.weather
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Disposable
@@ -40,24 +41,17 @@ class WeatherCodex : Disposable {
fun readFromJson(modname: String, file: File) = readFromJson(modname, file.path)
fun readFromJson(modname: String, fileHandle: FileHandle) {
readFromJsonValue(modname, JsonFetcher.invoke(fileHandle))
}
private val pathToImage = "weathers"
fun readFromJson(modname: String, path: String) {
/* JSON structure:
{
"skyboxGradColourMap": "colourmap/sky_colour.tga", // string (path to image) for dynamic. Image must be RGBA8888 or RGB888
"extraImages": [
// if any, it will be like:
sun01.tga,
clouds01.tga,
clouds02.tga,
auroraBlueViolet.tga
]
}
*/
val JSON = JsonFetcher(path)
readFromJsonValue(modname, JsonFetcher(path))
}
private fun readFromJsonValue(modname: String, JSON: com.badlogic.gdx.utils.JsonValue) {
val skyboxModel = JSON.getString("skyboxGradColourMap")
val lightboxModel = JSON.getString("daylightClut")