Complete addons

This commit is contained in:
Anibus 2025-06-07 23:41:33 +03:00
parent 47a01f17cd
commit 926156a1dc
15 changed files with 161 additions and 103 deletions

View File

@ -4,6 +4,7 @@ data class AddonRequirementDto (
val requirementBuildings: List<BuildingShortDto>,
val requirementBuildingsEither: List<BuildingShortDto>,
val requireAddon: BuildingAddonShortDto?,
val replaceWhenDone: Boolean,
val requirementsGlobalAddons: List<BuildingAddonShortDto>?,
val requiredTotalPop: Int?,
)

View File

@ -2,6 +2,7 @@ package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.building.BuildingAddonDto
import com.dowstats.data.dto.controllers.building.BuildingFullDto
import com.dowstats.data.entities.Weapon.HardpointPosition
import jakarta.persistence.*
@ -48,6 +49,9 @@ class Building {
@OneToMany(mappedBy = "building", cascade = [CascadeType.ALL])
var weapons: MutableSet<BuildingWeapon>? = null
@Transient
var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf()
fun toDto(buildingAddons: Set<BuildingAddonDto>?): BuildingFullDto =
BuildingFullDto(
id!!,

View File

@ -50,7 +50,7 @@ class BuildingAddon {
addonCostFaith = addonCostFaith,
addonCostSouls = addonCostSouls,
addonCostTime = addonCostTime,
addonModifiers = addonModifiers?.map { it.toDto() }?.toSet() ?: emptySet(),
addonModifiers = addonModifiers?.sortedBy { it.reference }?.map { it.toDto() }?.toSet() ?: emptySet(),
addonRequirement = addonRequirementDto,
icon = icon
)

View File

@ -1,6 +1,7 @@
package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.SergeantDto
import com.dowstats.data.entities.Weapon.HardpointPosition
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.*
@ -44,6 +45,9 @@ class Sergeant {
var detectRadius: Int? = null
var icon: String? = null
@Transient
var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf()
@OneToMany(mappedBy = "sergeant", cascade = [CascadeType.ALL])
var weapons: MutableSet<SergeantWeapon>? = null

View File

@ -37,11 +37,9 @@ class Weapon {
var haveEquipButton: Boolean = true
var modId: Long? = null
@Transient
var hardpoint: Int = 0
// for many-to-many persistance
data class HardpointPosition(val weaponId: Long, val hardpoint: Int, val order: Int)
@Transient
var hardpointOrder: Int = 0
@OneToMany(mappedBy="weapon", fetch = FetchType.EAGER, cascade = [(CascadeType.ALL)])
var weaponPiercings: List<WeaponArmorPiercing> = listOf()

View File

@ -46,6 +46,8 @@ class DowBuildingMappingService @Autowired constructor(
building.addons?.find { addon -> addon.filename == it.split("\\").last().replace(".lua", ".rgd") }
}
val replaceWhenDone = requirements?.find { it.reference == AddonRequirements.REFERENCE_REQUIREMENT_ADDON }?.replaceWhenDone ?: false
val requirementAddonGlobal =
requirements?.filter { it.reference == AddonRequirements.REFERENCE_GLOBAL_REQUIREMENT_ADDON }
?.mapNotNull { rgra ->
@ -86,13 +88,14 @@ class DowBuildingMappingService @Autowired constructor(
requirementBuildings ?: emptyList(),
requirementBuildingsEither ?: emptyList(),
requirementAddon?.toShortDto(),
replaceWhenDone,
requirementAddonGlobal,
requireCap
)
} else null
addon.toDto(addonRequirement)
}?.sortedBy { it.id }?.toSet()
}?.sortedBy { it.name }?.toSet()
return building.toDto(buildingAddons)
}

View File

@ -161,16 +161,9 @@ class BuildingAddonRgdExtractService @Autowired constructor(
val iconPathInMod = buildingData
.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, modName) }
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
}

View File

@ -102,9 +102,8 @@ class BuildingRgdExtractService @Autowired constructor(
val buildingWeapons = getBuildingWeapon(buildingData)?.mapNotNull { weaponData ->
weapons.find {
it.filename == weaponData.weaponFilename + ".rgd"
}.also {
it?.hardpoint = weaponData.hardpoint
it?.hardpointOrder = weaponData.hardpointOrder
}?.also {wp ->
building.weaponHardpoints.add(Weapon.HardpointPosition( wp.id!!, weaponData.hardpoint, weaponData.hardpointOrder))
}
}.orEmpty().toSet()
@ -211,16 +210,9 @@ class BuildingRgdExtractService @Autowired constructor(
.getRgdTableByName("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, modName) }
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}

View File

@ -6,7 +6,9 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.awt.image.BufferedImage
import java.io.File
import java.nio.file.Path
import javax.imageio.ImageIO
import kotlin.io.path.exists
@Service
@ -32,7 +34,7 @@ class IconsService @Autowired constructor(
val modFolder = modName?.let { "${File.separator}$modName" } ?: ""
val pathToSave = "${storageConfig.iconsStorage.replace("/", File.separator)}$modFolder" +
"${File.separator}${iconPathInMod.replace("\\", File.separator)}.png"
"${File.separator}${iconPathInMod.replace("/", File.separator).replace("\\", File.separator)}.png"
val directoryToSave = File(pathToSave.split(File.separator).dropLast(1).joinToString (File.separator))
if(!directoryToSave.exists()) directoryToSave.mkdirs()

View File

@ -4,6 +4,8 @@ import com.dowstats.configuration.StorageConfig
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 ModAttribPathService @Autowired constructor(
@ -39,6 +41,15 @@ class ModAttribPathService @Autowired constructor(
fun geBuildingAttribsPath(modFolderData: String, race: String): String =
"$modFolderData${File.separator}attrib${File.separator}ebps${File.separator}races${File.separator}$race${File.separator}structures"
fun getIconPath(modFolderData: String, iconName: String): String =
"$modFolderData${File.separator}art${File.separator}ui${File.separator}ingame${File.separator}$iconName.tga"
fun getIconPath(modFolderData: String, iconPath: String): String {
val iconValidEndPath = iconPath.replace("\\", File.separator).replace("/", File.separator)
val pathToIcon = "${File.separator}art${File.separator}ui${File.separator}ingame${File.separator}$iconValidEndPath.tga"
val modIcon = "$modFolderData$pathToIcon"
return if (Path.of(modIcon).exists()) modIcon else
"$pathToWanilaData$pathToIcon"
}
}

View File

@ -31,19 +31,21 @@ class ModParserService @Autowired constructor(
val defaultDictionary: MutableMap<Int, String> = mutableMapOf()
init {
RgdParserService::class.java.getClassLoader().getResourceAsStream("DXP2.ucs").bufferedReader(Charsets.UTF_8).lines().forEach {
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","")
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
defaultDictionary[key] = value
}
RgdParserService::class.java.getClassLoader().getResourceAsStream("W40k.ucs").bufferedReader(Charsets.UTF_8).lines().forEach {
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","")
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
defaultDictionary[key] = value
}
}
@ -61,7 +63,7 @@ class ModParserService @Autowired constructor(
.drop(1)
.filter { Files.isDirectory(it) }.map { it.name }.toList()
racesList.forEach{
racesList.forEach {
unitRepository.deleteAllByModIdAndRaceId(mod.id!!, it)
}
@ -78,8 +80,11 @@ class ModParserService @Autowired constructor(
}
private fun getModDictionary(mod: Mod, modFolderData: String): Map<Int,String>{
val folder = if (mod.technicalName == "multidungeon_rightpocalypse") modAttribPathService.getUcsFolderRus(modFolderData) else modAttribPathService.getUcsFolder(modFolderData)
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>()
@ -87,11 +92,13 @@ class ModParserService @Autowired constructor(
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) {
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","")
val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
modDictionary[key] = value
}
}
@ -99,7 +106,13 @@ class ModParserService @Autowired constructor(
return modDictionary
}
private fun saveUnits(modFolderData: String, weapons: Set<Weapon>, racesList: List<String>, mod: Mod, modDictionary: Map<Int, String>) {
private fun saveUnits(
modFolderData: String,
weapons: Set<Weapon>,
racesList: List<String>,
mod: Mod,
modDictionary: Map<Int, String>
) {
val races = raceRepository.findAll().toList()
val armorTypes = armorTypeRepository.findAll().toList()
@ -107,14 +120,34 @@ class ModParserService @Autowired constructor(
racesList.forEach { raceFolder ->
val classicRgdDataSquads =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getSbpsAttribsPath(modAttribPathService.pathToWanilaData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getSbpsAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataSquads =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getSbpsAttribsPath(modFolderData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getSbpsAttribsPath(
modFolderData,
raceFolder
)
)
val classicRgdDataUnits =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getEbpsTroopsAttribsPath(modAttribPathService.pathToWanilaData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getEbpsTroopsAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataUnits =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getEbpsTroopsAttribsPath(modFolderData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getEbpsTroopsAttribsPath(
modFolderData,
raceFolder
)
)
val modSquadsFull = classicRgdDataSquads + modRgdDataSquads
val modUnitsFull = classicRgdDataUnits + modRgdDataUnits
@ -127,7 +160,7 @@ class ModParserService @Autowired constructor(
val unitRgdData: List<RgdData> =
modUnitsFull[baseUnitName] ?: emptyList()
if(unitRgdData.isEmpty()){
if (unitRgdData.isEmpty()) {
log.warn("Can't find rgd data for unit $baseUnitName")
return@forEach
}
@ -154,7 +187,7 @@ class ModParserService @Autowired constructor(
try {
val unit = unitRepository.save(unitDataToSave.unit)
unit.weapons = unitDataToSave.unitWeapons?.map {weapon ->
unit.weapons = unitDataToSave.unitWeapons?.map { weapon ->
UnitWeapon().also {
it.unit = unit
it.weapon = weapon.weapon
@ -173,16 +206,20 @@ class ModParserService @Autowired constructor(
val sergeant = sergeantRepository.save(sergeantToSave.first.also {
it.unit = unit
})
sergeant.weapons = sergeantToSave.second.map {weapon ->
SergeantWeapon().also {
it.sergeant = sergeant
it.weapon = weapon
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 = weapon.hardpoint
swk.hardpointOrder = weapon.hardpointOrder
it.sergeantWeaponKey = SergeantWeaponKey().also { swk ->
swk.sergeantId = sergeant.id
swk.weaponId = weapon.id
swk.hardpoint = hardpointOrder.hardpoint
swk.hardpointOrder = hardpointOrder.order
}
}
}
}.toMutableSet()
@ -195,10 +232,12 @@ class ModParserService @Autowired constructor(
}
}
private fun saveBuildings(modFolderData: String,
weapons: Set<Weapon>,
racesList: List<String>,
mod: Mod, modDictionary: Map<Int, String>) {
private fun saveBuildings(
modFolderData: String,
weapons: Set<Weapon>,
racesList: List<String>,
mod: Mod, modDictionary: Map<Int, String>
) {
val races = raceRepository.findAll().toList()
val armorTypes = armorTypeRepository.findAll().toList()
@ -207,9 +246,19 @@ class ModParserService @Autowired constructor(
racesList.forEach { raceFolder ->
val classicRgdDataStructures =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.geBuildingAttribsPath(modAttribPathService.pathToWanilaData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.geBuildingAttribsPath(
modAttribPathService.pathToWanilaData,
raceFolder
)
)
val modRgdDataStructures =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.geBuildingAttribsPath(modFolderData, raceFolder))
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.geBuildingAttribsPath(
modFolderData,
raceFolder
)
)
val modStructuresFull = classicRgdDataStructures + modRgdDataStructures
@ -235,17 +284,21 @@ class ModParserService @Autowired constructor(
try {
val building = buildingRepository.save(structureDataToSave.building)
building.weapons = structureDataToSave.buildingWeapons.map {weapon ->
BuildingWeapon().also {
it.building = building
it.weapon = weapon
it.buildingWeaponKey = BuildingWeaponKey().also {
it.buildingId = building.id
it.weaponId = weapon.id
it.hardpoint = weapon.hardpoint
it.hardpointOrder = weapon.hardpointOrder
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)
@ -260,7 +313,8 @@ class ModParserService @Autowired constructor(
private fun getAddonsRgdData(modFolderData: String): Map<String, List<RgdData>> {
val classicAddons = rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/addons")
val classicAddons =
rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/addons")
val modAddons =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAddonAttribsPath(modFolderData))
@ -270,7 +324,8 @@ class ModParserService @Autowired constructor(
private fun saveWeapons(modFolderData: String, mod: Mod, modDictionary: Map<Int, String>): Set<Weapon> {
val classicWeapons = rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/weapon")
val classicWeapons =
rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/weapon")
val modWeapons =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getWeaponAttribsPath(modFolderData))

View File

@ -91,9 +91,8 @@ class SergeantRgdExtractService @Autowired constructor(
val sergeantWeapons = getSergeantWeapons(sergeantData)?.mapNotNull { weaponData ->
weapons.find {
it.filename == weaponData.weaponFilename + ".rgd"
}.also {
it?.hardpoint = weaponData.hardpoint
it?.hardpointOrder = weaponData.hardpointOrder
}?.also {wp ->
sergeant.weaponHardpoints.add(Weapon.HardpointPosition(wp.id!!, weaponData.hardpoint, weaponData.hardpointOrder))
}
}.orEmpty().toSet()
@ -189,16 +188,9 @@ class SergeantRgdExtractService @Autowired constructor(
.getRgdTableByName("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, modName) }
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
}

View File

@ -407,16 +407,9 @@ class UnitRgdExtractService @Autowired constructor(
.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, modName) }
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
}

View File

@ -170,14 +170,9 @@ class WeaponRgdExtractService @Autowired constructor(
val icon = try {
val iconPath = weaponUiInfo?.getStringByName("icon_name")
?.replace("/", File.separator)
val tgaIconPath = iconPath?.let {
val modIcon = modAttribPathService.getIconPath(modFolderData, it)
if(Path.of(modIcon).exists()) modIcon else
modAttribPathService.getIconPath(modAttribPathService.pathToWanilaData, it)
}
tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPath, it, mod.name) }
iconPath?.let { modAttribPathService.getIconPath(modFolderData, iconPath) }
?.let { iconsService.convertTgaToJpegImage(iconPath, it, mod.name) }
} catch (e: Exception) {
log.error("Error parsing ui icon path", e)
null

View File

@ -93,7 +93,22 @@
"name": "value",
"type": "varchar(256)"
}
},
}
],
"tableName": "addon_requirements"
}
}
]
}
},
{
"changeSet": {
"id": "Add mod_id column to building_addons",
"author": "anibus",
"changes": [
{
"addColumn": {
"columns": [
{
"column": {
"name": "mod_id",
@ -104,7 +119,7 @@
}
}
],
"tableName": "addon_requirements"
"tableName": "building_addons"
}
}
]