2025-03-15 21:43:20 +03:00

398 lines
15 KiB
Kotlin

package com.dowstats.service.w40k
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.dto.UnitDataToSave
import com.dowstats.data.entities.*
import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getIntByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.Path
import kotlin.io.path.exists
@Service
class UnitRgdExtractService @Autowired constructor(
private val modAttribPathService: ModAttribPathService,
private val iconsService: IconsService,
private val sergeantRgdExtractService: SergeantRgdExtractService,
) {
val log = LoggerFactory.getLogger(UnitRgdExtractService::class.java)
data class HealthAndMoraleDeathData(
val hitpoints: Double?,
val regeneration: Double?,
val moraleDeathPenalty: Double?,
)
data class MoraleData(
val max: Double?,
val broken: Double?,
val regeneration: Double?,
)
data class WeaponsData(
val hardpoint: Int,
val hardpointOrder: Int,
val weaponFilename: String,
)
data class SergeantData(
val filePath: String,
val cost: BuildCost,
)
data class MassData(
val mass: Int?,
val upTime: Double?,
)
data class UnitTexts(
val name: String?,
val description: String?,
)
fun extractToUnitEntity(
fileName: String,
modDictionary: Map<Int, String>,
squadData: List<RgdData>,
unitData: List<RgdData>,
weapons: Set<Weapon>,
race: String,
modFolderData: String,
modId: Long,
races: List<Race>,
armorTypes: List<ArmorType>,
modUnitsFull: Map<String, List<RgdData>>,
): UnitDataToSave {
val unit = DowUnit()
val unitRace = races.find { it.id == race } ?: throw Exception("Cant get race $race")
unit.race = unitRace
unit.armorType = getUnitArmorType(unitData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
unit.armorType2 = getUnitArmorType(unitData, armorTypes, "type_armour_2")
val nameAndDescription = getUnitNameAndDescription(squadData, modDictionary)
unit.name = nameAndDescription.name
unit.description = nameAndDescription.description
unit.filename = fileName
val buildCost = getBuildCost(unitData, squadData)
unit.buildCostRequisition = buildCost.requisition
unit.buildCostPower = buildCost.power
unit.buildCostPopulation = buildCost.population
unit.buildCostFaith = buildCost.faith
unit.buildCostSouls = buildCost.souls
unit.buildCostTime = buildCost.time
val squadCap = getSquadCap(squadData)
unit.capInfantry = squadCap.first?.toInt()
unit.capSupport = squadCap.second?.toInt()
val squadSize = getSquadSize(squadData)
unit.squadStartSize = squadSize.first?.toInt()
unit.squadMaxSize = squadSize.second?.toInt()
unit.squadLimit = getSquadLimit(squadData)?.toInt()
val healthData = getHealthAndMoraleDeathPenaltyData(unitData)
unit.health = healthData.hitpoints?.toInt()
unit.healthRegeneration = healthData.regeneration
unit.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt()
val moraleData = getMoraleData(squadData)
unit.moraleMax = moraleData.max?.toInt()
unit.moraleBroken = moraleData.broken?.toInt()
unit.moraleRegeneration = moraleData.regeneration?.toInt()
unit.moveSpeed = getUnitSpeed(unitData)?.toInt()
unit.sightRadius = getSightRadius(unitData)?.toInt()
unit.detectRadius = getDetectRadius(unitData)?.toInt()
val massData = getMassData(unitData)
unit.mass = massData.mass
unit.upTime = massData.upTime
val reinforceData = getReinforceRgdData(squadData)
val reinforceCostData = reinforceData?.getRgdTableByName("cost")
unit.reinforceCostRequisition = getReinforceRequisition(reinforceCostData)
unit.reinforceCostPower = getReinforcePower(reinforceCostData)
unit.reinforceCostPopulation = getReinforcePopulation(reinforceCostData)
unit.reinforceCostFaith = getReinforceFaith(reinforceCostData)
unit.reinforceTime = getReinforceTime(reinforceData)
val sergeantsData = getSergeantsData(squadData)
val sergeantsEntities: List<Pair<Sergeant, Set<Weapon>>>? = sergeantsData.first?.mapNotNull { sergeantData ->
val sergeantFile = sergeantData.filePath.split("\\").last().replace(".lua", ".rgd")
val sergeantRgdData = modUnitsFull[sergeantFile]
if(sergeantRgdData != null){
sergeantRgdExtractService.extractToSergeantEntity(
sergeantFile,
modDictionary,
sergeantRgdData,
weapons,
modFolderData,
sergeantData.cost,
armorTypes
)
} else null
}
unit.maxSergeants = sergeantsData.second
val unitIcon = convertIconAndReturnPath(squadData, modFolderData)
unit.icon = unitIcon
val unitWeapons = getUnitWeapons(unitData)?.mapNotNull { weaponData ->
weapons.find {
it.filename == weaponData.weaponFilename + ".rgd"
}.also {
it?.hardpoint = weaponData.hardpoint
it?.hardpointOrder = weaponData.hardpointOrder
}
}.orEmpty().toSet()
unit.modId = modId
return UnitDataToSave(unit, unitWeapons, sergeantsEntities)
}
fun getUnitRgdFileNameFromSquadData(squadRgdData: List<RgdData>): String? {
val baseUnitPath = squadRgdData.getRgdTableByName("squad_loadout_ext")
?.getRgdTableByName("trooper_base")
?.getStringByName("type")
return baseUnitPath?.split("\\")?.last()?.replace(".lua", ".rgd")
}
private fun getUnitArmorType(
unitData: List<RgdData>,
armorTypes: Iterable<ArmorType>,
armorTypeTableName: String
): ArmorType? {
val armorType = unitData.getRgdTableByName("type_ext")
?.getRgdTableByName(armorTypeTableName)
?.getStringByName("\$REF")
return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") }
}
private fun getBuildCost(unitData: List<RgdData>, squadData: List<RgdData>): BuildCost {
val cost = unitData.getRgdTableByName("cost_ext")
?.getRgdTableByName("time_cost")
val costResources = cost
?.getRgdTableByName("cost")
val minSquadSize = squadData.getRgdTableByName("squad_loadout_ext")
?.getDoubleByName("unit_min")?.toInt()
fun getCost(cost: Double?) =
cost?.let { it * (minSquadSize ?: 1) }
return BuildCost(
getCost(costResources?.getDoubleByName("requisition")),
getCost(costResources?.getDoubleByName("power")),
getCost(costResources?.getDoubleByName("population")),
getCost(costResources?.getDoubleByName("faith")),
getCost(costResources?.getDoubleByName("souls")),
getCost(cost?.getDoubleByName("time_seconds"))?.toInt()
)
}
private fun getUnitNameAndDescription(squadData: List<RgdData>, modDictionary: Map<Int, String>): UnitTexts {
val uiInfo = squadData.getRgdTableByName("squad_ui_ext")
?.getRgdTableByName("ui_info")
val nameRef = uiInfo?.getStringByName("screen_name_id")?.replace("$", "")
val name = nameRef?.let { try{modDictionary[it.toInt()]} catch (e: Exception) { null } }
val descriptionRefs = uiInfo?.getRgdTableByName("help_text_list")
?.map{(it.value as String).replace("$", "")}
?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
val description = try {
descriptionRefs?.map { modDictionary[it.toInt()] }?.joinToString ( "\n" )
} catch(e:Exception) {
log.warn("Error parsing ui description", e)
null
}
return UnitTexts(name, description)
}
private fun getSquadCap(squadData: List<RgdData>): Pair<Double?, Double?> {
val squadCap = squadData.getRgdTableByName("squad_cap_ext")
return Pair(
squadCap?.getDoubleByName("squad_cap_usage"),
squadCap?.getDoubleByName("support_cap_usage")
)
}
private fun getSquadSize(squadData: List<RgdData>): Pair<Double?, Double?> {
val squadSize = squadData.getRgdTableByName("squad_loadout_ext")
val unitMin = squadSize?.getDoubleByName("unit_min")
val unitMax = squadSize?.getDoubleByName("unit_max")
return Pair(unitMin, unitMax)
}
private fun getSquadLimit(squadData: List<RgdData>): Double? {
val requirements = squadData.getRgdTableByName("squad_requirement_ext")
?.getRgdTableByName("requirements")
return requirements?.map {
if (it.type == 100) {
val reqirementData = it.value as List<RgdData>
reqirementData.find { it.name == "max_squad_cap" || it.name == "max_cumulative_squad_cap" }?.value as Double?
} else null
}?.filterNotNull()?.firstOrNull()
}
private fun getHealthAndMoraleDeathPenaltyData(unitData: List<RgdData>): HealthAndMoraleDeathData {
val healthExt = unitData.getRgdTableByName("health_ext")
return HealthAndMoraleDeathData(
healthExt?.getDoubleByName("hitpoints"),
healthExt?.getDoubleByName("regeneration_rate"),
healthExt?.getDoubleByName("morale_death")
)
}
private fun getMoraleData(squadData: List<RgdData>): MoraleData {
val moraleData = squadData.getRgdTableByName("squad_morale_ext")
val max = moraleData?.getDoubleByName("max")
val broken = moraleData?.getDoubleByName("broken_min_morale")
val regeneration = moraleData?.getDoubleByName("rate_per_second")
return MoraleData(max, broken, regeneration)
}
private fun getMassData(unitData: List<RgdData>): MassData {
val massDataRgd = unitData
.getRgdTableByName("special_attack_physics_ext")
val unitMass = massDataRgd?.getIntByName("mass")
val unitUpTime = massDataRgd?.getDoubleByName("get_up_time")
return MassData(unitMass, unitUpTime)
}
private fun getUnitSpeed(unitData: List<RgdData>): Double? = unitData
.getRgdTableByName("moving_ext")
?.getDoubleByName("speed_max")
private fun getSightRadius(unitData: List<RgdData>): Double? = unitData
.getRgdTableByName("sight_ext")
?.getDoubleByName("sight_radius")
private fun getDetectRadius(unitData: List<RgdData>): Double? = unitData
.getRgdTableByName("sight_ext")
?.getDoubleByName("keen_sight_radius")
private fun getReinforceRgdData(squadData: List<RgdData>): List<RgdData>? = squadData
.getRgdTableByName("squad_reinforce_ext")
?.getRgdTableByName("cost")
private fun getReinforceRequisition(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("requisition")
private fun getReinforcePower(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("power")
private fun getReinforcePopulation(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("population")
private fun getReinforceFaith(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("faith")
private fun getReinforceTime(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("time_seconds")
private fun getUnitWeapons(reinforceData: List<RgdData>?): List<WeaponsData>? = reinforceData
?.getRgdTableByName("combat_ext")
?.getRgdTableByName("hardpoints")
?.mapNotNull { hardpoint ->
if (hardpoint.name.contains("hardpoint_")) {
val hardpointValue = hardpoint.name.replace("hardpoint_", "").toInt()
val hardpointTable = hardpoint.value as List<RgdData>
hardpointTable.getRgdTableByName("weapon_table")?.let {
it.mapNotNull { weapon ->
(weapon.value as? List<RgdData>)?.getStringByName("weapon")?.let {
if (it != "") {
WeaponsData(hardpointValue,
weapon.name.replace("weapon_", "").toInt(),
it.replace("weapon\\", "").replace(".lua", ""))
} else null
}
}
}
} else null
}?.flatten()
private fun getSergeantsData(squadData: List<RgdData>?): Pair<List<SergeantData>?, Int?> {
val squadTable = squadData
?.getRgdTableByName("squad_leader_ext")
val maxSergeants = squadTable?.getIntByName("max_leaders")
val sergeantsData = squadTable?.mapNotNull { sergeantData ->
if (sergeantData.name.contains("leader_")) {
val sergeantRgdTable = sergeantData.value as List<RgdData>
val sergeantLeaderFilePath = sergeantRgdTable.getRgdTableByName("leader")?.getStringByName("type")
if(sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else {
val cost = sergeantRgdTable.getRgdTableByName("cost_time")
val costResources = cost?.getRgdTableByName("cost")
SergeantData(sergeantLeaderFilePath,
BuildCost(
costResources?.getDoubleByName("requisition"),
costResources?.getDoubleByName("power"),
costResources?.getDoubleByName("population"),
costResources?.getDoubleByName("faith"),
costResources?.getDoubleByName("souls"),
cost?.getIntByName("time_seconds"),
)
)
}
} else null
}
return Pair(sergeantsData, maxSergeants)
}
private fun convertIconAndReturnPath(squadData: List<RgdData>, modFolderData: String): String? {
val iconPathInMod = squadData
.getRgdTableByName("squad_ui_ext")
?.getRgdTableByName("ui_info")
?.getStringByName("icon_name")
?.replace("/", File.separator)
val tgaIconPath = iconPathInMod?.let {
val modIcon = modAttribPathService.getIconPath(modFolderData, it)
if(Path.of(modIcon).exists()) modIcon else
modAttribPathService.getIconPath(modAttribPathService.pathToWanilaData, it)
}
return tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it) }
}
}