Files
Terrarum/src/net/torvald/terrarum/modulebasegame/SavegameMigrator.kt
2024-11-25 23:50:28 +09:00

176 lines
6.3 KiB
Kotlin

package net.torvald.terrarum.modulebasegame
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.BlockCodex
import net.torvald.terrarum.ItemCodex
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameitems.isBlock
import net.torvald.terrarum.gameitems.isDynamic
import net.torvald.terrarum.gameitems.isWall
import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
import net.torvald.util.SortedArrayList
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.findAnnotation
/**
* @param versionAnnotation If the version of the savegame meets the condition, the filter will be run
* - `all`: applies to all versions
* - `0.3.1`: applies to any past versions up to 0.3.1
* - `0.3.1+`: applies to any future versions including 0.3.1
* - `0.3.1!`: applies to verson 0.3.1 only
* - `0.3.1-0.3.4`: applies to version 0.3.1 to 0.3.4
* - `0.3.1-0.3.4;0.4.3-0.4.5`: applies to (0.3.1 to 0.3.4) and (0.4.3-0.4.5)
* - `0.3.1-0.3.4;0.5.0+`: applies to (0.3.1 to 0.3.4) and (0.5.0 and onward)
*
* Following syntaxes are considered as undefined behaviours:
* - `0.3.1-0.3.4;0.5.0`
*/
annotation class AppliedVersion(val versionAnnotation: String)
/**
* Created by minjaesong on 2023-09-29.
*/
internal object SavegameMigrator {
private fun Char.isNotAsciiDigit() = this.code !in 0x30..0x39
private fun toVersionIndex(a_dot_b_dot_c: String): Long {
val abc = a_dot_b_dot_c.split('.').map { it.toLong() }
if (abc.size != 3) throw IllegalArgumentException("Parse error: $a_dot_b_dot_c")
return abc[0].shl(48) or abc[1].shl(24) or abc[2]
}
private fun annotationMatches(fileVersion: Long, annotation: AppliedVersion?): Boolean {
if (annotation == null) return false
if (annotation.versionAnnotation.equals("all")) return true
var matched = false
annotation.versionAnnotation.split(";").forEach { query ->
// is range?
if (query.contains('-')) {
val from0 = query.substringBefore('-')
val to0 = query.substringAfter('-')
if (from0.last().isNotAsciiDigit() || to0.last().isNotAsciiDigit())
throw IllegalArgumentException("Illegal query '$query'")
val from = toVersionIndex(from0)
val to = toVersionIndex(to0)
if (from > to) throw IllegalArgumentException("Illegal query '$query'")
matched = matched or (fileVersion in from..to)
}
else if (query.endsWith('+') || query.endsWith('!')) {
val operator = query.last()
val specVersion = toVersionIndex(query.substringBefore(operator))
matched = matched or when (operator) {
'+' -> (fileVersion >= specVersion)
'!' -> (fileVersion == specVersion)
else -> throw IllegalArgumentException("Unknown operator '$operator' for query '$query'")
}
}
else {
matched = matched or (fileVersion <= toVersionIndex(query))
}
}
return matched
}
operator fun invoke(worldVersion: Long, playerVersion: Long, actors0: SortedArrayList<Actor>) {
if (worldVersion == playerVersion) {
this::class.declaredFunctions.filter {
annotationMatches(worldVersion, it.findAnnotation())
}.sortedBy { it.name }.forEach { func ->
printdbg(this, func.toString())
actors0.forEach { actor -> func.call(this, actor) }
}
}
else {
val nonPlayers = ArrayList<Actor>()
val players = ArrayList<IngamePlayer>()
for (actor in actors0) {
if (actor is IngamePlayer) players.add(actor)
else nonPlayers.add(actor)
}
this::class.declaredFunctions.filter {
annotationMatches(worldVersion, it.findAnnotation())
}.forEach { func ->
nonPlayers.forEach { actor -> func.call(this, actor) }
}
this::class.declaredFunctions.filter {
annotationMatches(playerVersion, it.findAnnotation())
}.forEach { func ->
players.forEach { player -> func.call(this, player) }
}
}
}
/***********************************/
/* INSERT MIGRATION FUNCTIONS HERE */
/***********************************/
@AppliedVersion("all")
fun b_mergeUnlitBlocksAndFillInInventory(actor: Actor) {
if (ItemCodex.isEmpty()) throw Error("ItemCodex is empty")
if (actor is Pocketed) {
val oldItemEquipped = actor.inventory.itemEquipped.copyOf()
val oldQuickSlot = actor.inventory.quickSlot.copyOf()
val oldItems = actor.inventory.clear()
oldItems.forEach { (itm, qty) ->
if (itm.isBlock() || itm.isWall()) {
if (!BlockCodex[itm].hasTag("AIR") && !BlockCodex[itm].isActorBlock) {
actor.inventory.add(itm, qty)
}
}
else {
actor.inventory.add(itm, qty)
}
}
oldItemEquipped.forEachIndexed { index, id0 ->
if (id0?.isDynamic() == true)
actor.inventory.itemEquipped[index] = id0
else {
val id = FixtureInventory.filterItem(ItemCodex[id0])
actor.inventory.itemEquipped[index] = id?.dynamicID
}
}
oldQuickSlot.forEachIndexed { index, id0 ->
if (id0?.isDynamic() == true)
actor.inventory.quickSlot[index] = id0
else {
val id = FixtureInventory.filterItem(ItemCodex[id0])
actor.inventory.quickSlot[index] = id?.dynamicID
}
}
}
}
@AppliedVersion("0.4.0")
fun a_updatePlayerEncumbrance(actor: Actor) {
if (actor is IngamePlayer && actor.actorValue.getAsInt("encumbrance") == 1000) {
actor.actorValue.set("encumbrance", 50000)
}
}
}