2025-08-18 12:20:24 +03:00

421 lines
16 KiB
Kotlin

package com.dowstats.service.w40k
import com.dowstats.data.entities.*
import com.dowstats.data.entities.research.Research
import com.dowstats.data.repositories.*
import com.dowstats.data.rgd.RgdData
import com.dowstats.service.postparsing.PostParsingService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.lang.Exception
import jakarta.transaction.Transactional
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Files
import kotlin.io.path.Path
import kotlin.io.path.name
@Service
class ModParserService @Autowired constructor(
val rgdParserService: RgdParserService,
val unitRgdExtractService: UnitRgdExtractService,
val researchRgdExtractService: ResearchRgdExtractService,
val raceRepository: RaceRepository,
val armorTypeRepository: ArmorTypeRepository,
val unitRepository: UnitRepository,
val sergeantRepository: SergeantRepository,
val weaponRgdExtractService: WeaponRgdExtractService,
val weaponRepository: WeaponRepository,
val modAttribPathService: ModAttribPathService,
val buildingRepository: BuildingRepository,
val buildingExtractService: BuildingRgdExtractService,
val postParsingService: PostParsingService,
private val researchRepository: ResearchRepository,
private val modRepository: ModRepository,
) {
val defaultDictionary: MutableMap<Int, String> = mutableMapOf()
init {
RgdParserService::class.java.getClassLoader().getResourceAsStream("DXP2.ucs").bufferedReader(Charsets.UTF_8)
.lines().forEach {
val kv = it.split("\\s+".toRegex())
if (kv.size < 2) return@forEach
val key = kv.first().filter { it.isDigit() }.toInt()
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
defaultDictionary[key] = value
}
RgdParserService::class.java.getClassLoader().getResourceAsStream("W40k.ucs").bufferedReader(Charsets.UTF_8)
.lines().forEach {
val kv = it.split("\\s+".toRegex())
if (kv.size < 2) return@forEach
val key = kv.first().filter { it.isDigit() }.toInt()
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
defaultDictionary[key] = value
}
}
val log = LoggerFactory.getLogger(ModParserService::class.java)
@Transactional
fun parseModFilesAndSaveToDb(mod: Mod) {
log.info("Start parse mod files ${mod.technicalName}:${mod.version}")
modRepository.clearModData(mod.id!!)
val modFolderData = modAttribPathService.getModFolderData(mod.technicalName!!, mod.version!!)
val racesList = Files.walk(Path(modAttribPathService.getSbpsAttribsFolderPath(modFolderData)), 1)
.toList()
.drop(1)
.filter { Files.isDirectory(it) }.map { it.name }.toList()
val armorTypes = if(mod.technicalName?.contains("UltimateApocalypse") == true)
armorTypeRepository.findByIsUa(true).toSet()
else armorTypeRepository.findByIsClassic(true).toSet()
racesList.forEach {
unitRepository.deleteAllByModIdAndRaceId(mod.id!!, it)
}
val modDictionary: Map<Int, String> = getModDictionary(mod, modFolderData)
log.info("Extract dictionaries from $modFolderData")
val enrichedModDictionary = defaultDictionary + modDictionary
val weapons = saveWeapons(modFolderData, mod, armorTypes, enrichedModDictionary)
val researches = saveResearches(modFolderData, mod, enrichedModDictionary)
saveUnits(modFolderData, weapons, racesList, armorTypes, mod, enrichedModDictionary)
saveBuildings(modFolderData, weapons, researches, racesList, armorTypes, mod, enrichedModDictionary)
modRepository.removeCampaignEntities(mod.id!!)
postParsingService.bindResearch(mod)
log.info("Complete parse mod ${mod.technicalName}:${mod.version}")
}
private fun getModDictionary(mod: Mod, modFolderData: String): Map<Int, String> {
val folder =
if (mod.technicalName == "multidungeon_rightpocalypse") modAttribPathService.getUcsFolderRus(modFolderData) else modAttribPathService.getUcsFolder(
modFolderData
)
val modDictionary = mutableMapOf<Int, String>()
File(folder).listFiles()?.forEach {
it.bufferedReader(Charsets.UTF_16).lines().forEach {
val kv = it.split("\\s+".toRegex())
if (kv.size < 2) return@forEach
val key = try {
kv.first().filter { it.isDigit() }.toInt()
} catch (e: Exception) {
log.warn("Cant' get key ${kv.first()} for dict")
0
}
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
modDictionary[key] = value
}
}
return modDictionary
}
private fun saveResearches(
modFolderData: String,
mod: Mod,
modDictionary: Map<Int, String>
): Set<Research> {
val classicRgdDataResearches =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getResearchAttribsPath(
modAttribPathService.pathToWanilaData,
)
)
val modRgdDataResearches =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getResearchAttribsPath(
modFolderData,
)
)
val researchesFull = classicRgdDataResearches + modRgdDataResearches
val researches = researchesFull.mapNotNull { researchData ->
try {
researchRgdExtractService.extractToResearchEntity(
researchData.key,
modDictionary,
researchData.value,
modFolderData,
mod,
)
} catch (e: Exception) {
log.error("Can't extract ${researchData.key}", e)
null
}
}
return try {
researchRepository.saveAll(researches).toSet()
} catch (e: Exception) {
throw e
}
}
private fun saveUnits(
modFolderData: String,
weapons: Set<Weapon>,
racesList: List<String>,
armorTypes: Set<ArmorType>,
mod: Mod,
modDictionary: Map<Int, String>
) {
val races = raceRepository.findAll().toList()
racesList.forEach { raceFolder ->
val classicRgdDataSquads =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getSbpsAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataSquads =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getSbpsAttribsPath(
modFolderData,
raceFolder
)
)
val classicRgdDataUnits =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getEbpsTroopsAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataUnits =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getEbpsTroopsAttribsPath(
modFolderData,
raceFolder
)
)
val modSquadsFull = classicRgdDataSquads + modRgdDataSquads
val modUnitsFull = classicRgdDataUnits + modRgdDataUnits
modSquadsFull.forEach { squadRgdData ->
val baseUnitName = unitRgdExtractService.getUnitRgdFileNameFromSquadData(squadRgdData.value)
?: throw Exception("Can't extract unit name from squad ${squadRgdData.key}")
log.info("Start extracting $raceFolder: $baseUnitName")
val unitRgdData: List<RgdData> =
modUnitsFull[baseUnitName] ?: emptyList()
if (unitRgdData.isEmpty()) {
log.warn("Can't find rgd data for unit $baseUnitName")
return@forEach
}
val unitDataToSave =
try {
unitRgdExtractService.extractToUnitEntity(
squadRgdData.key,
baseUnitName,
modDictionary,
squadRgdData.value,
unitRgdData,
weapons,
raceFolder,
modFolderData,
mod,
races,
armorTypes,
modUnitsFull
)
} catch (e: Exception) {
log.error("Can't extract $baseUnitName", e)
return@forEach
}
try {
val unit = unitRepository.save(unitDataToSave.unit)
unit.weapons = unitDataToSave.unitWeapons?.map { weapon ->
UnitWeapon().also {
it.unit = unit
it.weapon = weapon.weapon
it.unitWeaponKey = UnitWeaponKey().also {
it.unitId = unit.id
it.weaponId = weapon.weapon?.id
it.hardpoint = weapon.hardpoint
it.hardpointOrder = weapon.hardpointOrder
}
}
}?.toMutableSet()
unitRepository.save(unit)
unitDataToSave.sergeants?.forEach { sergeantToSave ->
val sergeant = sergeantRepository.save(sergeantToSave.first.also {
it.unit = unit
})
sergeant.weapons = sergeantToSave.second.flatMap { weapon ->
sergeant.weaponHardpoints
.filter { it.weaponId == weapon.id }
.map { hardpointOrder ->
SergeantWeapon().also {
it.sergeant = sergeant
it.weapon = weapon
it.sergeantWeaponKey = SergeantWeaponKey().also { swk ->
swk.sergeantId = sergeant.id
swk.weaponId = weapon.id
swk.hardpoint = hardpointOrder.hardpoint
swk.hardpointOrder = hardpointOrder.order
}
}
}
}.toMutableSet()
sergeantRepository.save(sergeant)
}
} catch (e: Exception) {
log.error("Cant save unit ${unitDataToSave.unit.name}", e)
throw e
}
}
}
}
private fun saveBuildings(
modFolderData: String,
weapons: Set<Weapon>,
researches: Set<Research>,
racesList: List<String>,
armorTypes: Set<ArmorType>,
mod: Mod, modDictionary: Map<Int, String>
) {
val races = raceRepository.findAll().toList()
val addonsRgdData = getAddonsRgdData(modFolderData)
racesList.forEach { raceFolder ->
val classicRgdDataStructures =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.geBuildingAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataStructures =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.geBuildingAttribsPath(
modFolderData,
raceFolder
)
)
val modStructuresFull = classicRgdDataStructures + modRgdDataStructures
if(raceFolder == "npc") return@forEach
val race = races.find { it.id == raceFolder }
if(race == null){
log.warn("Can't find race $raceFolder")
return@forEach
}
val raceUnits = unitRepository.findByModIdAndRace(mod.id!!, race)
modStructuresFull.forEach { structure ->
val structureDataToSave =
try {
buildingExtractService.extractToBuildingEntity(
structure.key,
modDictionary,
structure.value,
weapons,
researches,
raceUnits,
race,
modFolderData,
mod,
races,
armorTypes,
addonsRgdData,
)
} catch (e: Exception) {
log.error("Can't extract ${structure.key}", e)
return@forEach
}
try {
val building = buildingRepository.save(structureDataToSave.building)
building.weapons = structureDataToSave.buildingWeapons.flatMap { weapon ->
building.weaponHardpoints
.filter { it.weaponId == weapon.id }
.map { hardpointOrder ->
BuildingWeapon().also {
it.building = building
it.weapon = weapon
it.buildingWeaponKey = BuildingWeaponKey().also {
it.buildingId = building.id
it.weaponId = weapon.id
it.hardpoint = hardpointOrder.hardpoint
it.hardpointOrder = hardpointOrder.order
}
}
}
}.toMutableSet()
buildingRepository.save(building)
} catch (e: Exception) {
throw e
}
}
}
}
private fun getAddonsRgdData(modFolderData: String): Map<String, List<RgdData>> {
val classicAddons =
rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/addons")
val modAddons =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAddonAttribsPath(modFolderData))
return classicAddons + modAddons
}
private fun saveWeapons(modFolderData: String, mod: Mod, armorTypes: Set<ArmorType>, modDictionary: Map<Int, String>): Set<Weapon> {
val classicWeapons =
rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/weapon")
val modWeapons =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getWeaponAttribsPath(modFolderData))
val allWeapons = classicWeapons + modWeapons
val weaponsToSave = allWeapons.mapNotNull {
weaponRgdExtractService.extractToWeaponEntity(it.key, it.value, mod, armorTypes, modFolderData, modDictionary)
}
return try {
weaponRepository.saveAll(weaponsToSave).toSet()
} catch (e: Exception) {
throw e
}
}
}