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, squadData: List, unitData: List, weapons: Set, race: String, modFolderData: String, modId: Long, races: List, armorTypes: List, modUnitsFull: Map>, ): 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>>? = 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): 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, armorTypes: Iterable, 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, squadData: List): 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, modDictionary: Map): 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): Pair { val squadCap = squadData.getRgdTableByName("squad_cap_ext") return Pair( squadCap?.getDoubleByName("squad_cap_usage"), squadCap?.getDoubleByName("support_cap_usage") ) } private fun getSquadSize(squadData: List): Pair { 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): Double? { val requirements = squadData.getRgdTableByName("squad_requirement_ext") ?.getRgdTableByName("requirements") return requirements?.map { if (it.type == 100) { val reqirementData = it.value as List 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): 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): 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): 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): Double? = unitData .getRgdTableByName("moving_ext") ?.getDoubleByName("speed_max") private fun getSightRadius(unitData: List): Double? = unitData .getRgdTableByName("sight_ext") ?.getDoubleByName("sight_radius") private fun getDetectRadius(unitData: List): Double? = unitData .getRgdTableByName("sight_ext") ?.getDoubleByName("keen_sight_radius") private fun getReinforceRgdData(squadData: List): List? = squadData .getRgdTableByName("squad_reinforce_ext") ?.getRgdTableByName("cost") private fun getReinforceRequisition(reinforceData: List?): Int? = reinforceData ?.getIntByName("requisition") private fun getReinforcePower(reinforceData: List?): Int? = reinforceData ?.getIntByName("power") private fun getReinforcePopulation(reinforceData: List?): Int? = reinforceData ?.getIntByName("population") private fun getReinforceFaith(reinforceData: List?): Int? = reinforceData ?.getIntByName("faith") private fun getReinforceTime(reinforceData: List?): Int? = reinforceData ?.getIntByName("time_seconds") private fun getUnitWeapons(reinforceData: List?): List? = 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 hardpointTable.getRgdTableByName("weapon_table")?.let { it.mapNotNull { weapon -> (weapon.value as? List)?.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?): Pair?, 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 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, 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) } } }