Upgraded Kotlin to 1.4.30 and Gradle to 6.8.2. Set the Kotlin JVM version to 11. Added a subproject for offline asset generation which, at the moment, can generate the list of item types. Ported unitxt and ItemPMT parsing.

This commit is contained in:
Daan Vanden Bosch 2021-03-06 13:33:04 +01:00
parent 93e57012e7
commit 321fb3a475
408 changed files with 14684 additions and 14016 deletions

View File

@ -1,16 +1,18 @@
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
plugins {
kotlin("js") version "1.4.20" apply false
kotlin("multiplatform") version "1.4.20" apply false
kotlin("plugin.serialization") version "1.4.20" apply false
kotlin("js") version "1.4.30" apply false
kotlin("multiplatform") version "1.4.30" apply false
kotlin("plugin.serialization") version "1.4.30" apply false
}
tasks.wrapper {
gradleVersion = "6.6.1"
gradleVersion = "6.8.2"
}
subprojects {
project.extra["jvmVersion"] = "11"
project.extra["coroutinesVersion"] = "1.4.2"
project.extra["kotlinLoggingVersion"] = "2.0.2"
project.extra["ktorVersion"] = "1.4.3"

View File

@ -1,5 +1,5 @@
plugins {
kotlin("jvm") version "1.4.20"
kotlin("jvm") version "1.4.30"
`java-gradle-plugin`
}

View File

@ -5,6 +5,14 @@ plugins {
val coroutinesVersion: String by project.ext
val kotlinLoggingVersion: String by project.extra
val jvmVersion: String by project.extra
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = jvmVersion
}
}
kotlin {
js {
browser {}

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -130,7 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

21
gradlew.bat vendored
View File

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -20,6 +20,14 @@ val kotlinLoggingVersion: String by project.extra
val serializationVersion: String by project.extra
val slf4jVersion: String by project.extra
val jvmVersion: String by project.extra
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = jvmVersion
}
}
kotlin {
js {
browser {

View File

@ -0,0 +1,257 @@
package world.phantasmal.lib.fileFormats
import world.phantasmal.lib.cursor.Cursor
class ItemPmt(
val statBoosts: List<PmtStatBoost>,
val frames: List<PmtFrame>,
val barriers: List<PmtFrame>,
val units: List<PmtUnit>,
val tools: List<List<PmtTool>>,
val weapons: List<List<PmtWeapon>>,
)
class PmtStatBoost(
val stat1: Int,
val stat2: Int,
val amount1: Int,
val amount2: Int,
)
class PmtFrame(
val id: Int,
val type: Int,
val skin: Int,
val teamPoints: Int,
val dfp: Int,
val evp: Int,
val blockParticle: Int,
val blockEffect: Int,
val frameClass: Int,
val reserved1: Int,
val requiredLevel: Int,
val efr: Int,
val eth: Int,
val eic: Int,
val edk: Int,
val elt: Int,
val dfpRange: Int,
val evpRange: Int,
val statBoost: Int,
val techBoost: Int,
val unknown1: Int,
)
class PmtUnit(
val id: Int,
val type: Int,
val skin: Int,
val teamPoints: Int,
val stat: Int,
val statAmount: Int,
val plusMinus: Int,
val reserved: ByteArray,
)
class PmtTool(
val id: Int,
val type: Int,
val skin: Int,
val teamPoints: Int,
val amount: Int,
val tech: Int,
val cost: Int,
val itemFlag: Int,
val reserved: ByteArray,
)
class PmtWeapon(
val id: Int,
val type: Int,
val skin: Int,
val teamPoints: Int,
val weaponClass: Int,
val reserved1: Int,
val minAtp: Int,
val maxAtp: Int,
val reqAtp: Int,
val reqMst: Int,
val reqAta: Int,
val mst: Int,
val maxGrind: Int,
val photon: Int,
val special: Int,
val ata: Int,
val statBoost: Int,
val projectile: Int,
val photonTrail1x: Int,
val photonTrail1y: Int,
val photonTrail2x: Int,
val photonTrail2y: Int,
val photonType: Int,
val unknown1: ByteArray,
val techBoost: Int,
val comboType: Int,
)
fun parseItemPmt(cursor: Cursor): ItemPmt {
val index = parseRel(cursor, parseIndex = true).index
// This size (65268) of this table seems wrong, so we pass in a hard-coded value.
val statBoosts = parseStatBoosts(cursor, index[305].offset, 52)
val frames = parseFrames(cursor, index[7].offset, index[7].size)
val barriers = parseFrames(cursor, index[8].offset, index[8].size)
val units = parseUnits(cursor, index[9].offset, index[9].size)
val tools = mutableListOf<List<PmtTool>>()
val weapons = mutableListOf<List<PmtWeapon>>()
for (i in 11..37) {
tools.add(parseTools(cursor, index[i].offset, index[i].size))
}
for (i in 38..275) {
weapons.add(parseWeapons(cursor, index[i].offset, index[i].size))
}
return ItemPmt(
statBoosts,
frames,
barriers,
units,
tools,
weapons,
)
}
private fun parseStatBoosts(cursor: Cursor, offset: Int, size: Int): List<PmtStatBoost> {
cursor.seekStart(offset)
val statBoosts = mutableListOf<PmtStatBoost>()
repeat(size) {
statBoosts.add(PmtStatBoost(
stat1 = cursor.uByte().toInt(),
stat2 = cursor.uByte().toInt(),
amount1 = cursor.short().toInt(),
amount2 = cursor.short().toInt(),
))
}
return statBoosts
}
private fun parseFrames(cursor: Cursor, offset: Int, size: Int): List<PmtFrame> {
cursor.seekStart(offset)
val frames = mutableListOf<PmtFrame>()
repeat(size) {
frames.add(PmtFrame(
id = cursor.int(),
type = cursor.short().toInt(),
skin = cursor.short().toInt(),
teamPoints = cursor.int(),
dfp = cursor.short().toInt(),
evp = cursor.short().toInt(),
blockParticle = cursor.uByte().toInt(),
blockEffect = cursor.uByte().toInt(),
frameClass = cursor.uByte().toInt(),
reserved1 = cursor.uByte().toInt(),
requiredLevel = cursor.uByte().toInt(),
efr = cursor.uByte().toInt(),
eth = cursor.uByte().toInt(),
eic = cursor.uByte().toInt(),
edk = cursor.uByte().toInt(),
elt = cursor.uByte().toInt(),
dfpRange = cursor.uByte().toInt(),
evpRange = cursor.uByte().toInt(),
statBoost = cursor.uByte().toInt(),
techBoost = cursor.uByte().toInt(),
unknown1 = cursor.short().toInt(),
))
}
return frames
}
private fun parseUnits(cursor: Cursor, offset: Int, size: Int): List<PmtUnit> {
cursor.seekStart(offset)
val units = mutableListOf<PmtUnit>()
repeat(size) {
units.add(PmtUnit(
id = cursor.int(),
type = cursor.short().toInt(),
skin = cursor.short().toInt(),
teamPoints = cursor.int(),
stat = cursor.short().toInt(),
statAmount = cursor.short().toInt(),
plusMinus = cursor.uByte().toInt(),
reserved = cursor.byteArray(3),
))
}
return units
}
private fun parseTools(cursor: Cursor, offset: Int, size: Int): List<PmtTool> {
cursor.seekStart(offset)
val tools = mutableListOf<PmtTool>()
repeat(size) {
tools.add(PmtTool(
id = cursor.int(),
type = cursor.short().toInt(),
skin = cursor.short().toInt(),
teamPoints = cursor.int(),
amount = cursor.short().toInt(),
tech = cursor.short().toInt(),
cost = cursor.int(),
itemFlag = cursor.uByte().toInt(),
reserved = cursor.byteArray(3),
))
}
return tools
}
private fun parseWeapons(cursor: Cursor, offset: Int, size: Int): List<PmtWeapon> {
cursor.seekStart(offset)
val weapons = mutableListOf<PmtWeapon>()
repeat(size) {
weapons.add(PmtWeapon(
id = cursor.int(),
type = cursor.short().toInt(),
skin = cursor.short().toInt(),
teamPoints = cursor.int(),
weaponClass = cursor.uByte().toInt(),
reserved1 = cursor.uByte().toInt(),
minAtp = cursor.short().toInt(),
maxAtp = cursor.short().toInt(),
reqAtp = cursor.short().toInt(),
reqMst = cursor.short().toInt(),
reqAta = cursor.short().toInt(),
mst = cursor.short().toInt(),
maxGrind = cursor.uByte().toInt(),
photon = cursor.byte().toInt(),
special = cursor.uByte().toInt(),
ata = cursor.uByte().toInt(),
statBoost = cursor.uByte().toInt(),
projectile = cursor.uByte().toInt(),
photonTrail1x = cursor.byte().toInt(),
photonTrail1y = cursor.byte().toInt(),
photonTrail2x = cursor.byte().toInt(),
photonTrail2y = cursor.byte().toInt(),
photonType = cursor.byte().toInt(),
unknown1 = cursor.byteArray(5),
techBoost = cursor.uByte().toInt(),
comboType = cursor.uByte().toInt(),
))
}
return weapons
}

View File

@ -0,0 +1,28 @@
package world.phantasmal.lib.fileFormats
import world.phantasmal.lib.cursor.Cursor
import kotlin.math.min
class Unitxt(val categories: List<List<String>>)
fun parseUnitxt(cursor: Cursor): Unitxt {
val categoryCount = cursor.int()
val entryCounts = cursor.intArray(categoryCount)
val categoryEntryOffsets: List<IntArray> = entryCounts.map { entryCount ->
cursor.intArray(entryCount)
}
val categories = categoryEntryOffsets.map { entryOffsets ->
entryOffsets.map { entryOffset ->
cursor.seekStart(entryOffset)
cursor.stringUtf16(
min(1024, cursor.bytesLeft),
nullTerminated = true,
dropRemaining = true,
)
}
}
return Unitxt(categories)
}

View File

@ -0,0 +1,22 @@
package world.phantasmal.lib.fileFormats
import world.phantasmal.lib.test.LibTestSuite
import world.phantasmal.lib.test.readFile
import kotlin.test.Test
import kotlin.test.assertEquals
class ItemPmtTests : LibTestSuite() {
@Test
fun parseBasicItemPmt() = testAsync {
val itemPmt = parseItemPmt(readFile("/ItemPMT.bin"))
val saber = itemPmt.weapons[1][0]
assertEquals(177, saber.id)
assertEquals(40, saber.minAtp)
assertEquals(55, saber.maxAtp)
assertEquals(30, saber.ata)
assertEquals(35, saber.maxGrind)
assertEquals(30, saber.reqAtp)
}
}

View File

@ -2,6 +2,14 @@ plugins {
kotlin("multiplatform")
}
val jvmVersion: String by project.extra
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = jvmVersion
}
}
kotlin {
js {
browser {}

View File

@ -7,6 +7,7 @@ include(
":test-utils",
":web",
":web:assembly-worker",
":web:assets-generation",
":web:shared",
":webui"
)

View File

@ -4,6 +4,14 @@ plugins {
val coroutinesVersion: String by project.ext
val jvmVersion: String by project.extra
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = jvmVersion
}
}
kotlin {
js {
browser {}

View File

@ -6,7 +6,7 @@ import world.phantasmal.lib.asm.dataFlowAnalysis.ControlFlowGraph
import world.phantasmal.lib.asm.dataFlowAnalysis.getMapDesignations
import world.phantasmal.lib.asm.dataFlowAnalysis.getStackValue
import world.phantasmal.web.shared.*
import world.phantasmal.web.shared.AssemblyProblem
import world.phantasmal.web.shared.messages.*
import kotlin.math.min
import world.phantasmal.lib.asm.AssemblyProblem as AssemblerAssemblyProblem

View File

@ -0,0 +1,27 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
}
val jvmVersion: String by project.extra
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = jvmVersion
}
}
dependencies {
implementation(project(":lib"))
implementation(project(":web:shared"))
}
tasks.register<JavaExec>("generateAssets") {
val outputFile = File(buildDir, "generatedAssets")
outputs.dir(outputFile)
classpath = sourceSets.main.get().runtimeClasspath
main = "world.phantasmal.web.assetsGeneration.Main"
args = listOf(outputFile.absolutePath)
}

View File

@ -0,0 +1,219 @@
package world.phantasmal.web.assetsGeneration
import kotlinx.serialization.encodeToString
import world.phantasmal.core.splice
import world.phantasmal.lib.buffer.Buffer
import world.phantasmal.lib.compression.prs.prsDecompress
import world.phantasmal.lib.cursor.cursor
import world.phantasmal.lib.fileFormats.ItemPmt
import world.phantasmal.lib.fileFormats.parseItemPmt
import world.phantasmal.lib.fileFormats.parseUnitxt
import world.phantasmal.web.shared.JSON_FORMAT_PRETTY
import world.phantasmal.web.shared.dto.*
import java.io.File
import java.util.Comparator.comparing
object Ephinea {
/**
* ItemPMT.bin and ItemPT.gsl comes from stock Tethealla. ItemPT.gsl is not used at the moment.
* unitxt_j.prs comes from the Ephinea client.
* TODO: manual fixes:
* - Clio is equipable by HUnewearls
* - Red Ring has a requirement of 180, not 108
*/
fun generateAssets(outputDir: File) {
val items = loadItems(loadItemNames())
File(outputDir, "item_types.ephinea.json")
.writeText(JSON_FORMAT_PRETTY.encodeToString(items))
}
/**
* Extracts item names from unitxt file.
*/
private fun loadItemNames(): List<String> {
val unitxtBuffer =
object {}::class.java.getResourceAsStream(
"/ephinea/client/data/unitxt_j.prs"
).use { Buffer.fromByteArray(it.readBytes()) }
val unitxt = parseUnitxt(prsDecompress(unitxtBuffer.cursor()).unwrap())
val itemNames = unitxt.categories[1].toMutableList()
// Strip custom Ephinea items until we have the Ephinea ItemPMT.bin.
itemNames.splice(177, 50, emptyList())
itemNames.splice(639, 59, emptyList())
return itemNames
}
/**
* Loads items from ItemPMT.
*/
private fun loadItems(itemNames: List<String>): List<ItemType> {
val itemPmtBuffer =
object {}::class.java.getResourceAsStream(
"/ephinea/ship-config/param/ItemPMT.bin"
).use { Buffer.fromByteArray(it.readBytes()) }
val itemPmt = parseItemPmt(itemPmtBuffer.cursor())
val itemTypes = mutableListOf<ItemType>()
val ids = mutableSetOf<Int>()
fun checkId(id: Int, type: String, name: String) {
check(ids.add(id)) {
"""Trying to add $type with ID $id ($name) but ID already exists."""
}
}
for ((categoryI, category) in itemPmt.weapons.withIndex()) {
for ((i, weapon) in category.withIndex()) {
val id = (categoryI shl 8) + i
val name = itemNames[weapon.id]
checkId(id, "weapon", name)
itemTypes.add(WeaponItemType(
id,
name,
weapon.minAtp,
weapon.maxAtp,
weapon.ata,
weapon.maxGrind,
weapon.reqAtp,
))
}
}
for ((i, frame) in itemPmt.frames.withIndex()) {
val id = 0x10100 + i
val name = itemNames[frame.id]
checkId(id, "frame", name)
val stats = getStatBoosts(itemPmt, frame.statBoost)
itemTypes.add(FrameItemType(
id,
name,
stats.atp,
stats.ata,
minEvp = frame.evp + stats.minEvp,
maxEvp = frame.evp + stats.minEvp + frame.evpRange,
minDfp = frame.dfp + stats.minDfp,
maxDfp = frame.dfp + stats.minDfp + frame.dfpRange,
stats.mst,
stats.hp,
stats.lck,
))
}
for ((i, barrier) in itemPmt.barriers.withIndex()) {
val id = 0x10200 + i
val name = itemNames[barrier.id]
checkId(id, "barrier", name)
val stats = getStatBoosts(itemPmt, barrier.statBoost)
itemTypes.add(BarrierItemType(
id,
name,
stats.atp,
stats.ata,
minEvp = barrier.evp + stats.minEvp,
maxEvp = barrier.evp + stats.minEvp + barrier.evpRange,
minDfp = barrier.dfp + stats.minDfp,
maxDfp = barrier.dfp + stats.minDfp + barrier.dfpRange,
stats.mst,
stats.hp,
stats.lck,
))
}
for ((i, unit) in itemPmt.units.withIndex()) {
val id = 0x10300 + i
val name = itemNames[unit.id]
checkId(id, "unit", name)
itemTypes.add(UnitItemType(
id,
name,
))
}
for ((categoryI, category) in itemPmt.tools.withIndex()) {
for ((i, tool) in category.withIndex()) {
val id = (0x30000 or (categoryI shl 8)) + i
val name = itemNames[tool.id]
checkId(id, "tool", name)
itemTypes.add(ToolItemType(
id,
name,
))
}
}
itemTypes.sortWith(comparing({ it.name }, String.CASE_INSENSITIVE_ORDER))
return itemTypes
}
}
private class Boosts(
val atp: Int,
val ata: Int,
val minEvp: Int,
val minDfp: Int,
val mst: Int,
val hp: Int,
val lck: Int,
)
private fun getStatBoosts(itemPmt: ItemPmt, index: Int): Boosts {
val statBoosts = itemPmt.statBoosts[index]
val amount = statBoosts.amount1
var atp = 0
var ata = 0
var minEvp = 0
var minDfp = 0
var mst = 0
var hp = 0
var lck = 0
when (statBoosts.stat1) {
1 -> atp += amount
2 -> ata += amount
3 -> minEvp += amount
4 -> minDfp += amount
5 -> mst += amount
6 -> hp += amount
7 -> lck += amount
8 -> {
atp += amount
ata += amount
minEvp += amount
minDfp += amount
mst += amount
hp += amount
lck += amount
}
9 -> atp -= amount
10 -> ata -= amount
11 -> minEvp -= amount
12 -> minDfp -= amount
13 -> mst -= amount
14 -> hp -= amount
15 -> lck -= amount
16 -> {
atp -= amount
ata -= amount
minEvp -= amount
minDfp -= amount
mst -= amount
hp -= amount
lck -= amount
}
}
return Boosts(atp, ata, minEvp, minDfp, mst, hp, lck)
}

View File

@ -0,0 +1,17 @@
package world.phantasmal.web.assetsGeneration
import java.io.File
object Main {
@JvmStatic
fun main(args: Array<String>) {
require(args.isNotEmpty()) {
"Expected at least one argument denoting the directory where assets should be generated."
}
val outputDir = File(args.first())
outputDir.mkdirs()
Ephinea.generateAssets(outputDir)
}
}

Some files were not shown because too many files have changed in this diff Show More