398 lines
15 KiB
Kotlin
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) }
|
|
}
|
|
|
|
}
|