2026-08-02 14:23:43 +03:00

483 lines
20 KiB
Kotlin

package com.dowstats.service.w40k
import com.dowstats.Metadata
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.dto.UnitDataToSave
import com.dowstats.data.dto.WeaponsData
import com.dowstats.data.entities.*
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.fillDataFromRgdTable
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.filterUseless
import com.dowstats.data.entities.ability.Ability
import com.dowstats.data.entities.sergant.Sergeant
import com.dowstats.data.entities.unit.DowUnit
import com.dowstats.data.entities.unit.Jump
import com.dowstats.data.entities.unit.JumpRequirements
import com.dowstats.data.entities.unit.UnitModifiers
import com.dowstats.data.entities.unit.UnitMoraleBrakeModifiers
import com.dowstats.data.entities.unit.UnitRequirements
import com.dowstats.data.entities.weapon.Weapon
import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
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
@Service
class UnitRgdExtractService @Autowired constructor(
private val commonParseRgdService: CommonParseRgdService,
private val sergeantRgdExtractService: SergeantRgdExtractService,
private val deathExplosionRgdExtractService: DeathExplosionRgdExtractService,
) {
val log = LoggerFactory.getLogger(UnitRgdExtractService::class.java)
data class HealthAndMoraleDeathData(
val hitpoints: Double?,
val armour: Double?,
val regeneration: Double?,
val moraleDeathPenalty: Double?,
val maxRepairers: Int?,
)
data class RepairData(
val healthPerSecond: Int?,
val percentCost: Int?,
)
data class MoraleData(
val max: Double?,
val broken: Double?,
val regeneration: Double?,
val unitMoraleBrakeModifiers: List<UnitMoraleBrakeModifiers>,
)
data class SergeantData(
val filePath: String,
val cost: BuildCost,
)
data class MassData(
val mass: Int?,
val upTime: Double?,
)
fun extractToUnitEntity(
fileNameSquad: String,
fileNameUnit: String,
modDictionary: Map<Int, String>,
squadData: List<RgdData>,
unitData: List<RgdData>,
weapons: Set<Weapon>,
abilities: Set<Ability>,
race: String,
modFolderData: String,
mod: Mod,
races: List<Race>,
armorTypes: Set<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 = commonParseRgdService.getArmorType(unitData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
unit.armorType2 = commonParseRgdService.getArmorType(unitData, armorTypes, "type_armour_2")
val squadUiInfo = squadData.getRgdTableByName("squad_ui_ext")
?.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileNameSquad")
val squadUiData = commonParseRgdService.getUiInfo(squadUiInfo, modDictionary, modFolderData, mod, log)
unit.name = squadUiData.name?.take(4000)
unit.description = squadUiData.description
unit.icon = squadUiData.iconPath
unit.filenameSquad = fileNameSquad
unit.filenameUnit = fileNameUnit
unit.uiIndexHint = unitData.getRgdTableByName("ui_ext")?.getIntByName("ui_index_hint") ?: 0
unit.uiHotkeyName = unitData.getRgdTableByName("ui_ext")?.getStringByName("ui_hotkey_name")
unit.haveReinforceMenu = squadData.getRgdTableByName("squad_reinforce_ext") != null
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.armour = healthData.armour
unit.healthRegeneration = healthData.regeneration
unit.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt()
unit.repairMax = healthData.maxRepairers
val repairData = getRepairData(unitData)
unit.repairSpeed = repairData.healthPerSecond
unit.repairCostPercent = repairData.percentCost
val incomeData = commonParseRgdService.getResourceIncome(unitData.getRgdTableByName("resource_ext"))
unit.faithIncome = incomeData.faith
unit.powerIncome = incomeData.power
unit.requisitionIncome = incomeData.requisition
val moraleData = getMoraleData(unit, squadData)
unit.moraleMax = moraleData.max?.toInt()
unit.moraleBroken = moraleData.broken?.toInt()
unit.moraleRegeneration = moraleData.regeneration?.toInt()
unit.unitMoraleBrakeModifiers = moraleData.unitMoraleBrakeModifiers.toMutableSet()
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 reinforceCostRgdData = getReinforceCostRgdData(squadData)
val reinforceCostData = commonParseRgdService.getBuildCost(reinforceCostRgdData)
unit.reinforceCostRequisition = reinforceCostData.requisition
unit.reinforceCostPower = reinforceCostData.power
unit.reinforceCostPopulation = reinforceCostData.population
unit.reinforceCostFaith = reinforceCostData.faith
unit.reinforceCostSouls = reinforceCostData.souls
unit.reinforceTime = reinforceCostData.time
unit.mobValue = getMobValue(unitData)
unit.unitModifiers = geUnitModifiers(unit, unitData, squadData).toMutableSet()
unit.unitRequirements = getUnitRequirements(unit, squadData).toMutableSet()
unit.jump = getJump(unit, squadData)
unit.abilities = commonParseRgdService.getAbilities(unitData, abilities).toMutableSet()
unit.deathExplosions.addAll(deathExplosionRgdExtractService.getDeathExplosions(unitData, armorTypes).toMutableSet())
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,
mod,
sergeantRgdData,
weapons,
abilities,
modFolderData,
sergeantData.cost,
armorTypes
)
} else null
}
unit.maxSergeants = sergeantsData.second
val unitWeapons = getUnitWeapons(unitData, weapons)
unit.modId = mod.id
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 getBuildCost(unitData: List<RgdData>, squadData: List<RgdData>): BuildCost {
val cost = commonParseRgdService.getBuildCost(unitData.getRgdTableByName("cost_ext")?.getRgdTableByName("time_cost"))
val minSquadSize = squadData.getRgdTableByName("squad_loadout_ext")
?.getDoubleByName("unit_min")?.toInt()
fun getCost(cost: Double?) =
cost?.let { it * (minSquadSize ?: 1) }
return BuildCost(
getCost(cost.requisition),
getCost(cost.power),
getCost(cost.population),
getCost(cost.faith),
getCost(cost.souls),
getCost(cost.time)
)
}
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")
val canRepair = healthExt?.getBooleanByName("can_be_repaired")
return HealthAndMoraleDeathData(
healthExt?.getDoubleByName("hitpoints"),
healthExt?.getDoubleByName("armour")?.minus(100),
healthExt?.getDoubleByName("regeneration_rate"),
healthExt?.getDoubleByName("morale_death"),
canRepair?.let { if (it) healthExt.getIntByName("max_repairers") else null }
)
}
private fun getRepairData(unitData: List<RgdData>): RepairData {
val repairExt = unitData.getRgdTableByName("repair_ext")
return RepairData(
repairExt?.getIntByName("health_repaired_per_second"),
repairExt?.getIntByName("percent_of_target_cost_for_full_repair"),
)
}
private fun getMoraleData(unit: DowUnit, 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")
val unitMoraleBreakModifiers = moraleData?.getRgdTableByName("broken_modifiers")?.mapNotNull { m ->
if (m.name.contains("modifier_")) {
val mTable = m.value as List<RgdData>
if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua" ||
mTable.getStringByName("\$REF") == "modifiers\\morale_break_event.lua") null else {
UnitMoraleBrakeModifiers().also {
it.unit = unit
}.fillDataFromRgdTable(mTable)
}
} else null
} ?: emptyList()
return MoraleData(max, broken, regeneration, unitMoraleBreakModifiers)
}
private fun getMobValue(unitData: List<RgdData>): Double? =
unitData.getRgdTableByName("mob_ext")
?.getDoubleByName("mob_value")
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 getReinforceCostRgdData(squadData: List<RgdData>): List<RgdData>? = squadData
.getRgdTableByName("squad_reinforce_ext")
?.getRgdTableByName("cost")
private fun getUnitWeapons(unitData: List<RgdData>?, weapons: Set<Weapon>): List<WeaponsData>? = unitData
?.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 != "") {
val weaponFileName = it.replace("weapon\\", "").replace(".rgd", "").replace(".lua", "")
val weaponEntity = weapons.find {
it.filename?.replace(".rgd", "") == weaponFileName
}
if(weaponEntity == null){
log.warn("Can't find weapon $weaponFileName")
null
} else {
WeaponsData(
hardpointValue,
weapon.name.replace("weapon_", "").toInt(),
weaponEntity
)
}
} 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 {
SergeantData(
sergeantLeaderFilePath,
commonParseRgdService.getBuildCost(sergeantRgdTable.getRgdTableByName("cost_time"))
)
}
} else null
}
return Pair(sergeantsData, maxSergeants)
}
private fun geUnitModifiers(unit: DowUnit, unitData: List<RgdData>, squadData: List<RgdData>): List<UnitModifiers> {
fun getUnitModifiersFromTable(table: List<RgdData>?) : List<UnitModifiers> = table?.mapNotNull { m ->
if (m.name.contains("modifier_")) {
val mTable = m.value as List<RgdData>
if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua") null else {
UnitModifiers().also {
it.unit = unit
}.fillDataFromRgdTable(mTable)
}
} else null
}?.filterUseless() ?: emptyList()
fun getSquadModifiersFromTable(table: List<RgdData>?) : List<UnitModifiers> = table?.mapNotNull { m ->
if (m.name.contains("modifier_")) {
val mTable = m.value as List<RgdData>
val modifier = mTable.getRgdTableByName("modifier")
val maxLifetime = mTable.getDoubleByName("max_lifetime")
if (modifier?.getStringByName("\$REF") == "modifiers\\no_modifier.lua" || modifier == null) null else {
UnitModifiers().also {
it.unit = unit
it.maxLifeTime = maxLifetime
}.fillDataFromRgdTable(modifier)
}
} else null
}?.filterUseless() ?: emptyList()
val unitModifiersTable = unitData.getRgdTableByName("modifier_apply_ext")?.getRgdTableByName("modifiers")
val squadModifiersTable = squadData.getRgdTableByName("squad_modifier_apply_ext")?.getRgdTableByName("modifiers")
return getUnitModifiersFromTable(unitModifiersTable) + getSquadModifiersFromTable(squadModifiersTable)
}
private fun getUnitRequirements(unit: DowUnit, squadData: List<RgdData>): List<UnitRequirements> {
val requirements = squadData.getRgdTableByName("squad_requirement_ext")?.getRgdTableByName("requirements")
return requirements?.mapNotNull { r ->
if (r.name.contains("required_")) {
val rTable = r.value as List<RgdData>
if (rTable.getStringByName("\$REF")?.contains(Metadata.Requirements.REFERENCE_REQUIREMENT_NONE) == true) null else {
UnitRequirements().also {
it.unit = unit
it.reference = rTable.getStringByName("\$REF")
it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, unit.filenameSquad!!, log)
}
}
} else null
} ?: emptyList()
}
private fun getJump(unit: DowUnit, squadData: List<RgdData>): Jump? {
val squadJumpExt = squadData.getRgdTableByName("squad_jump_ext") ?: return null
return Jump().also { jump ->
jump.unit = unit
jump.chargeRegeneration = squadJumpExt.getDoubleByName("charge_regeneration")
jump.goUpTime = squadJumpExt.getDoubleByName("go_up_time")
jump.jumpDistanceMax = squadJumpExt.getDoubleByName("jump_distance_max")
jump.breakdownTime = squadJumpExt.getDoubleByName("breakdown_time")
jump.chargeJumpCostMin = squadJumpExt.getDoubleByName("charge_jump_cost_min")
jump.jumpHeight = squadJumpExt.getDoubleByName("jump_height")
jump.chargeMax = squadJumpExt.getDoubleByName("charge_max")
jump.setupTime = squadJumpExt.getDoubleByName("setup_time")
jump.chargeJumpCostMax = squadJumpExt.getDoubleByName("charge_jump_cost_max")
jump.goDownTime = squadJumpExt.getDoubleByName("go_down_time")
jump.teleport = squadJumpExt.getBooleanByName("teleport") ?: false
jump.jumpTimeMax = squadJumpExt.getDoubleByName("jump_time_max")
jump.jumpTimeMin = squadJumpExt.getDoubleByName("jump_time_min")
jump.jumpMinHeight = squadJumpExt.getDoubleByName("jump_min_height")
// Parse jump requirements
val requirements = squadJumpExt.getRgdTableByName("requirements")
jump.jumpRequirements = requirements?.mapNotNull { r ->
if (r.name.contains("required_")) {
val rTable = r.value as List<RgdData>
if (rTable.getStringByName("\$REF")?.contains(Metadata.Requirements.REFERENCE_REQUIREMENT_NONE) == true) null else {
JumpRequirements().also {
it.jump = jump
it.reference = rTable.getStringByName("\$REF")
it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, unit.filenameSquad!!, log)
}
}
} else null
}?.toMutableSet() ?: mutableSetOf()
}
}
}