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 = 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 = 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 { val folder = if (mod.technicalName == "multidungeon_rightpocalypse") modAttribPathService.getUcsFolderRus(modFolderData) else modAttribPathService.getUcsFolder( modFolderData ) val modDictionary = mutableMapOf() 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 ): Set { 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, racesList: List, armorTypes: Set, mod: Mod, modDictionary: Map ) { 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 = 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, researches: Set, racesList: List, armorTypes: Set, mod: Mod, modDictionary: Map ) { 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> { 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, modDictionary: Map): Set { 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 } } }