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, ) 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, squadData: List, unitData: List, weapons: Set, abilities: Set, race: String, modFolderData: String, mod: Mod, races: List, armorTypes: Set, modUnitsFull: Map>, ): 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>>? = 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): 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, squadData: List): 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): 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") 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): 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): 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 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): Double? = unitData.getRgdTableByName("mob_ext") ?.getDoubleByName("mob_value") 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 getReinforceCostRgdData(squadData: List): List? = squadData .getRgdTableByName("squad_reinforce_ext") ?.getRgdTableByName("cost") private fun getUnitWeapons(unitData: List?, weapons: Set): List? = 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 hardpointTable.getRgdTableByName("weapon_table")?.let { it.mapNotNull { weapon -> (weapon.value as? List)?.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?): 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 { SergeantData( sergeantLeaderFilePath, commonParseRgdService.getBuildCost(sergeantRgdTable.getRgdTableByName("cost_time")) ) } } else null } return Pair(sergeantsData, maxSergeants) } private fun geUnitModifiers(unit: DowUnit, unitData: List, squadData: List): List { fun getUnitModifiersFromTable(table: List?) : List = table?.mapNotNull { m -> if (m.name.contains("modifier_")) { val mTable = m.value as List 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?) : List = table?.mapNotNull { m -> if (m.name.contains("modifier_")) { val mTable = m.value as List 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): List { val requirements = squadData.getRgdTableByName("squad_requirement_ext")?.getRgdTableByName("requirements") return requirements?.mapNotNull { r -> if (r.name.contains("required_")) { val rTable = r.value as List 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): 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 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() } } }