diff --git a/pom.xml b/pom.xml
index 4dc63c7..38e2d2e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,6 +106,12 @@
kotlin-test
1.7.22
+
+ io.mockk
+ mockk-jvm
+ 1.13.17
+ test
+
diff --git a/src/main/kotlin/com/dowstats/controllers/AssetsController.kt b/src/main/kotlin/com/dowstats/controllers/AssetsController.kt
index 9867fd0..907a602 100644
--- a/src/main/kotlin/com/dowstats/controllers/AssetsController.kt
+++ b/src/main/kotlin/com/dowstats/controllers/AssetsController.kt
@@ -14,9 +14,11 @@ class AssetsController @Autowired constructor(
val iconService: IconsService,
) {
- @GetMapping("/icon/{raceIconFolder}/{imageName}")
- fun getUnits(@PathVariable raceIconFolder: String,
+ @GetMapping("/icon/{modName}/{raceIconFolder}/{imageName}")
+ fun getUnits(
+ @PathVariable modName: String,
+ @PathVariable raceIconFolder: String,
@PathVariable imageName: String,): ByteArray? {
- return iconService.returnIcon(raceIconFolder, imageName)
+ return iconService.returnIcon(modName, raceIconFolder, imageName)
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/controllers/BuildingsController.kt b/src/main/kotlin/com/dowstats/controllers/BuildingsController.kt
new file mode 100644
index 0000000..c4bc2ed
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/controllers/BuildingsController.kt
@@ -0,0 +1,47 @@
+package com.dowstats.controllers
+
+import com.dowstats.data.dto.controllers.RaceBuildings
+import com.dowstats.data.dto.controllers.RaceUnits
+import com.dowstats.data.entities.Building
+import com.dowstats.data.entities.DowUnit
+import com.dowstats.data.repositories.BuildingRepository
+import com.dowstats.data.repositories.UnitRepository
+import com.dowstats.service.datamaps.DowBuildingMappingService
+import com.dowstats.service.datamaps.DowUnitMappingService
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.bind.annotation.DeleteMapping
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+
+@RestController
+@RequestMapping("api/v1/buildings")
+class BuildingsController @Autowired constructor(
+ val dowBuildingMappingService: DowBuildingMappingService,
+ val buildingRepository: BuildingRepository,
+) {
+
+ @GetMapping("/{modId}/{raceId}")
+ fun getBuildingsByModAndRace(@PathVariable modId: Long,
+ @PathVariable raceId: String): RaceBuildings {
+ return dowBuildingMappingService.findBuildingsByModAndRace(modId, raceId)
+ }
+
+ @GetMapping("/mod/{modId}")
+ fun getUnitsByMod(@PathVariable modId: Long,): List {
+ return dowBuildingMappingService.findBuildingsByMod(modId)
+ }
+
+
+ @GetMapping("/{buildingId}")
+ fun getById(@PathVariable buildingId: Long): Building {
+ return buildingRepository.findById(buildingId).get()
+ }
+
+ @DeleteMapping
+ fun removeAll() {
+ buildingRepository.deleteAll()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/data/dto/BuildingDataToSave.kt b/src/main/kotlin/com/dowstats/data/dto/BuildingDataToSave.kt
new file mode 100644
index 0000000..ac22e60
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/dto/BuildingDataToSave.kt
@@ -0,0 +1,11 @@
+package com.dowstats.data.dto
+
+import com.dowstats.data.entities.Building
+import com.dowstats.data.entities.DowUnit
+import com.dowstats.data.entities.Sergeant
+import com.dowstats.data.entities.Weapon
+
+data class BuildingDataToSave(
+ val building: Building,
+ val buildingWeapons: Set,
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/data/dto/UnitDataToSave.kt b/src/main/kotlin/com/dowstats/data/dto/UnitDataToSave.kt
index a76778d..8c13d59 100644
--- a/src/main/kotlin/com/dowstats/data/dto/UnitDataToSave.kt
+++ b/src/main/kotlin/com/dowstats/data/dto/UnitDataToSave.kt
@@ -4,8 +4,14 @@ import com.dowstats.data.entities.DowUnit
import com.dowstats.data.entities.Sergeant
import com.dowstats.data.entities.Weapon
+data class WeaponsData(
+ val hardpoint: Int,
+ val hardpointOrder: Int,
+ val weapon: Weapon,
+)
+
data class UnitDataToSave(
val unit: DowUnit,
- val unitWeapons: Set,
+ val unitWeapons: List?,
val sergeants: List>>?,
)
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/data/dto/controllers/BuildingDto.kt b/src/main/kotlin/com/dowstats/data/dto/controllers/BuildingDto.kt
new file mode 100644
index 0000000..81e2846
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/dto/controllers/BuildingDto.kt
@@ -0,0 +1,16 @@
+package com.dowstats.data.dto.controllers
+
+import com.dowstats.data.entities.Race
+
+data class RaceBuildings(
+ val race: Race,
+ val buildings: List,
+)
+
+data class BuildingDto(
+ val name: String,
+ val icon: String,
+ val id: Long,
+ val armourTypeName: String,
+ val canDetect: Boolean,
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/data/entities/AddonModifiers.kt b/src/main/kotlin/com/dowstats/data/entities/AddonModifiers.kt
new file mode 100644
index 0000000..1bb9ea5
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/entities/AddonModifiers.kt
@@ -0,0 +1,23 @@
+package com.dowstats.data.entities
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import jakarta.persistence.*
+
+
+@Entity
+@Table(name = "addon_modifiers")
+class AddonModifiers {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null
+
+ @ManyToOne
+ @JoinColumn(name = "addon_id", nullable = false)
+ @JsonIgnore
+ var addon: BuildingAddon? = null
+
+ var references: String? = null
+ var usageType: String? = null
+ var value: String? = null
+}
diff --git a/src/main/kotlin/com/dowstats/data/entities/AddonRequires.kt b/src/main/kotlin/com/dowstats/data/entities/AddonRequires.kt
new file mode 100644
index 0000000..d148516
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/entities/AddonRequires.kt
@@ -0,0 +1,25 @@
+package com.dowstats.data.entities
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import jakarta.persistence.*
+
+
+@Entity
+@Table(name = "addon_requires")
+class AddonRequires {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null
+
+ @ManyToOne
+ @JoinColumn(name = "addon_id", nullable = false)
+ @JsonIgnore
+ var addon: BuildingAddon? = null
+
+ var references: String? = null
+
+ var replaceWhenDone: Boolean = false
+
+ var value: String? = null
+}
diff --git a/src/main/kotlin/com/dowstats/data/entities/Building.kt b/src/main/kotlin/com/dowstats/data/entities/Building.kt
new file mode 100644
index 0000000..d744b36
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/entities/Building.kt
@@ -0,0 +1,50 @@
+package com.dowstats.data.entities
+
+import jakarta.persistence.*
+
+
+@Entity
+@Table(name = "buildings")
+class Building {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null
+
+ @ManyToOne
+ @JoinColumn(name = "race_id", nullable = false)
+ var race: Race? = null
+
+ @ManyToOne
+ @JoinColumn(name = "armour_type_id", nullable = false)
+ var armorType: ArmorType? = null
+
+ @ManyToOne
+ @JoinColumn(name = "armour_type_2_id")
+ var armorType2: ArmorType? = null
+
+ var name: String? = null
+ var description: String? = null
+ var filename: String? = null
+ var buildCostRequisition: Double? = null
+ var buildCostPower: Double? = null
+ var buildCostPopulation: Double? = null
+ var buildCostFaith: Double? = null
+ var buildCostSouls: Double? = null
+ var buildCostTime: Int? = null
+ var health: Int? = null
+ var healthRegeneration: Double? = null
+ var sightRadius: Int? = null
+ var detectRadius: Int? = null
+ var repairMax: Int? = null
+ var icon: String? = null
+ var modId: Long? = null
+
+ @OneToMany(mappedBy = "building", cascade = [CascadeType.ALL])
+ var addons: MutableSet? = null
+
+ @OneToMany(mappedBy = "building", cascade = [CascadeType.ALL])
+ var weapons: MutableSet? = null
+
+
+}
diff --git a/src/main/kotlin/com/dowstats/data/entities/BuildingAddon.kt b/src/main/kotlin/com/dowstats/data/entities/BuildingAddon.kt
new file mode 100644
index 0000000..76cc9df
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/entities/BuildingAddon.kt
@@ -0,0 +1,38 @@
+package com.dowstats.data.entities
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import jakarta.persistence.*
+
+
+@Entity
+@Table(name = "building_addons")
+class BuildingAddon {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null
+
+ @ManyToOne
+ @JoinColumn(name = "building_id", nullable = false)
+ @JsonIgnore
+ var building: Building? = null
+
+ var name: String? = null
+ var description: String? = null
+ var filename: String? = null
+ var addonCostRequisition: Double? = null
+ var addonCostPower: Double? = null
+ var addonCostPopulation: Double? = null
+ var addonCostFaith: Double? = null
+ var addonCostSouls: Double? = null
+ var addonCostTime: Int? = null
+
+ @OneToMany(mappedBy = "addon", cascade = [CascadeType.ALL])
+ var addonModifiers: MutableSet? = null
+
+ @OneToMany(mappedBy = "addon", cascade = [CascadeType.ALL])
+ var addonRequires: MutableSet? = null
+
+ var icon: String? = null
+
+}
diff --git a/src/main/kotlin/com/dowstats/data/entities/BuildingWeapon.kt b/src/main/kotlin/com/dowstats/data/entities/BuildingWeapon.kt
new file mode 100644
index 0000000..6e5d25e
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/entities/BuildingWeapon.kt
@@ -0,0 +1,42 @@
+package com.dowstats.data.entities
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import jakarta.persistence.*
+import java.io.Serializable
+
+
+@Embeddable
+class BuildingWeaponKey : Serializable {
+ @Column(name = "building_id")
+ var buildingId: Long? = null
+
+ @Column(name = "weapon_id")
+ var weaponId: Long? = null
+
+ @Column(nullable = false)
+ var hardpoint: Int = 0
+
+ @Column(nullable = false)
+ var hardpointOrder: Int = 0
+}
+
+@Entity
+@Table(name = "buildings_weapons")
+class BuildingWeapon {
+
+ @EmbeddedId
+ @JsonIgnore
+ var buildingWeaponKey: BuildingWeaponKey? = null
+
+ @ManyToOne
+ @MapsId("buildingId")
+ @JoinColumn(name = "building_id")
+ @JsonIgnore
+ var building: Building? = null
+
+ @ManyToOne
+ @MapsId("weaponId")
+ @JoinColumn(name = "weapon_id")
+ var weapon: Weapon? = null
+
+}
diff --git a/src/main/kotlin/com/dowstats/data/entities/SergantWeapon.kt b/src/main/kotlin/com/dowstats/data/entities/SergantWeapon.kt
index 5b0ced2..505d059 100644
--- a/src/main/kotlin/com/dowstats/data/entities/SergantWeapon.kt
+++ b/src/main/kotlin/com/dowstats/data/entities/SergantWeapon.kt
@@ -13,6 +13,11 @@ class SergeantWeaponKey : Serializable {
@Column(name = "weapon_id")
var weaponId: Long? = null
+ @Column(nullable = false)
+ var hardpoint: Int = 0
+
+ @Column(nullable = false)
+ var hardpointOrder: Int = 0
}
@Entity
@@ -33,11 +38,4 @@ class SergeantWeapon {
@MapsId("weaponId")
@JoinColumn(name = "weapon_id")
var weapon: Weapon? = null
-
- @Column(nullable = false)
- var hardpoint: Int = 0
-
- @Column(nullable = false)
- var hardpointOrder: Int = 0
-
}
diff --git a/src/main/kotlin/com/dowstats/data/entities/UnitWeapon.kt b/src/main/kotlin/com/dowstats/data/entities/UnitWeapon.kt
index 6689782..1a85d53 100644
--- a/src/main/kotlin/com/dowstats/data/entities/UnitWeapon.kt
+++ b/src/main/kotlin/com/dowstats/data/entities/UnitWeapon.kt
@@ -13,6 +13,12 @@ class UnitWeaponKey : Serializable {
@Column(name = "weapon_id")
var weaponId: Long? = null
+ @Column(name = "hardpoint")
+ var hardpoint: Int = 0
+
+ @Column(nullable = false)
+ var hardpointOrder: Int = 0
+
}
@Entity
@@ -34,10 +40,4 @@ class UnitWeapon {
@JoinColumn(name = "weapon_id")
var weapon: Weapon? = null
- @Column(nullable = false)
- var hardpoint: Int = 0
-
- @Column(nullable = false)
- var hardpointOrder: Int = 0
-
}
diff --git a/src/main/kotlin/com/dowstats/data/entities/Weapon.kt b/src/main/kotlin/com/dowstats/data/entities/Weapon.kt
index e5485a3..85d4892 100644
--- a/src/main/kotlin/com/dowstats/data/entities/Weapon.kt
+++ b/src/main/kotlin/com/dowstats/data/entities/Weapon.kt
@@ -18,6 +18,7 @@ class Weapon {
var costTimeSeconds: Int? = null
var accuracy: Double? = null
var reloadTime: Double? = null
+ var minRange: Double? = null
var maxRange: Double? = null
var setupTime: Double? = null
var accuracyReductionMoving: Double? = null
@@ -25,6 +26,9 @@ class Weapon {
var maxDamage: Double? = null
var minDamageValue: Double? = null
var moraleDamage: Double? = null
+ var damageRadius: Double? = null
+ var throwForceMin: Double? = null
+ var throwForceMax: Double? = null
var isMeleeWeapon: Boolean = true
var canAttackAir: Boolean = true
var canAttackGround: Boolean = true
diff --git a/src/main/kotlin/com/dowstats/data/repositories/AddonRepository.kt b/src/main/kotlin/com/dowstats/data/repositories/AddonRepository.kt
new file mode 100644
index 0000000..1e3ccc9
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/repositories/AddonRepository.kt
@@ -0,0 +1,10 @@
+package com.dowstats.data.repositories
+
+import com.dowstats.data.entities.Building
+import com.dowstats.data.entities.BuildingAddon
+import com.dowstats.data.entities.DowUnit
+import com.dowstats.data.entities.Race
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.CrudRepository
+
+interface AddonRepository : CrudRepository
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/data/repositories/BuildingRepository.kt b/src/main/kotlin/com/dowstats/data/repositories/BuildingRepository.kt
new file mode 100644
index 0000000..989c651
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/data/repositories/BuildingRepository.kt
@@ -0,0 +1,19 @@
+package com.dowstats.data.repositories
+
+import com.dowstats.data.entities.Building
+import com.dowstats.data.entities.DowUnit
+import com.dowstats.data.entities.Race
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.CrudRepository
+
+interface BuildingRepository : CrudRepository{
+ @Query("""
+ select b, a1, a2
+ from Building b
+ left join fetch ArmorType a1 on b.armorType.id = a1.id
+ left join fetch ArmorType a2 on b.armorType2.id = a2.id
+ where b.modId = :modId
+ and (:race is null or b.race = :race)
+ """)
+ fun findByModIdAndRace(modId: Long, race: Race?): List
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/service/datamaps/DowBuildingMappingService.kt b/src/main/kotlin/com/dowstats/service/datamaps/DowBuildingMappingService.kt
new file mode 100644
index 0000000..698cd91
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/service/datamaps/DowBuildingMappingService.kt
@@ -0,0 +1,65 @@
+package com.dowstats.service.datamaps
+
+import com.dowstats.data.dto.controllers.BuildingDto
+import com.dowstats.data.dto.controllers.RaceBuildings
+import com.dowstats.data.dto.controllers.RaceUnits
+import com.dowstats.data.dto.controllers.UnitDto
+import com.dowstats.data.entities.Building
+import com.dowstats.data.entities.DowUnit
+import com.dowstats.data.entities.Race
+import com.dowstats.data.repositories.BuildingRepository
+import com.dowstats.data.repositories.RaceRepository
+import com.dowstats.data.repositories.UnitRepository
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+@Service
+class DowBuildingMappingService @Autowired constructor(
+ val buildingRepository: BuildingRepository,
+ val raceRepository: RaceRepository
+) {
+
+ fun findBuildingsByMod(modId: Long): List {
+ return getAllBuildings(modId).groupBy { it.race }.mapNotNull {raceUnits ->
+ RaceBuildings(raceUnits.key ?: throw Exception("Race is null"), raceUnits.value.toBuildingDto())
+ }
+ }
+
+ fun findBuildingsByModAndRace(modId: Long, race: String): RaceBuildings {
+ val buildings = getAllBuildings(modId, race)
+ val raceEntity = race.let{ raceRepository.findById(race) ?: throw Exception("Race $race not found") }
+ return RaceBuildings(raceEntity, buildings.toBuildingDto())
+ }
+
+
+
+ private fun List.toBuildingDto(): List =
+ this.mapNotNull {
+ val name = it.name ?: it.filename
+ val icon = it.icon
+ if (name == null || icon == null) null else BuildingDto(name, icon, it.id!!,
+ it.armorType?.name!!,
+ (it.detectRadius ?: 0) > 0)
+ }
+
+
+ private fun getAllBuildings(modId: Long, race: String? = null): List {
+ val raceEntity = race?.let{ raceRepository.findById(race) ?: throw Exception("Race $race not found") }
+ return filterCompanyUnits(buildingRepository.findByModIdAndRace(modId, raceEntity))
+ }
+
+ private fun filterCompanyUnits(buildings: List): List =
+ buildings.filter {
+ it.filename?.contains("_sp.") != true
+ && it.filename?.contains("_sp_") != true
+ && it.filename?.contains("sp_eldar_") != true
+ && it.filename?.contains("_dxp3.") != true
+ && it.filename?.contains("_dxp3_") != true
+ && it.filename?.contains("_nis.") != true
+ && it.filename?.contains("_exarch_council.") != true
+ && it.filename?.contains("_dark_reapers_base.") != true
+ && it.filename?.contains("tau_squad_slave_murdered") != true
+ && it.filename?.contains("space_marine_single_player_only_drop_pod_building_2.rgd") != true
+ && it.filename?.contains("space_marine_single_player_only_drop_pod_building.rgd") != true
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/dowstats/service/datamaps/DowUnitService.kt b/src/main/kotlin/com/dowstats/service/datamaps/DowUnitMappingService.kt
similarity index 100%
rename from src/main/kotlin/com/dowstats/service/datamaps/DowUnitService.kt
rename to src/main/kotlin/com/dowstats/service/datamaps/DowUnitMappingService.kt
diff --git a/src/main/kotlin/com/dowstats/service/integrations/ModStorageIntegrationService.kt b/src/main/kotlin/com/dowstats/service/integrations/ModStorageIntegrationService.kt
index 92ab5f5..f753d92 100644
--- a/src/main/kotlin/com/dowstats/service/integrations/ModStorageIntegrationService.kt
+++ b/src/main/kotlin/com/dowstats/service/integrations/ModStorageIntegrationService.kt
@@ -54,14 +54,19 @@ class ModStorageIntegrationService(
.sortedBy { it.id }
newVersions.map { toSave ->
- downloadAndExtractMod(toSave.technicalName, toSave.version)
- val savedMod = modRepository.save(Mod().also {
- it.version = toSave.version
- it.isBeta = toSave.isBeta
- it.name = "Dowstats balance mod"
- it.technicalName = toSave.technicalName
- })
- modParserService.parceModFilesAndSaveToDb(savedMod)
+ try{
+ downloadAndExtractMod(toSave.technicalName, toSave.version)
+ val savedMod = modRepository.save(Mod().also {
+ it.version = toSave.version
+ it.isBeta = toSave.isBeta
+ it.name = "Dowstats balance mod"
+ it.technicalName = toSave.technicalName
+ })
+ modParserService.parceModFilesAndSaveToDb(savedMod)
+ } catch (e: Exception) {
+ log.error("Error while download and extract mod", e)
+ }
+
}
}
diff --git a/src/main/kotlin/com/dowstats/service/w40k/BuildingAddonRgdExtractService.kt b/src/main/kotlin/com/dowstats/service/w40k/BuildingAddonRgdExtractService.kt
new file mode 100644
index 0000000..7566a4e
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/service/w40k/BuildingAddonRgdExtractService.kt
@@ -0,0 +1,116 @@
+package com.dowstats.service.w40k
+
+import com.dowstats.data.dto.BuildCost
+import com.dowstats.data.entities.*
+import com.dowstats.data.rgd.RgdData
+import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
+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
+import java.io.File
+import java.nio.file.Path
+import kotlin.io.path.exists
+
+@Service
+class BuildingAddonRgdExtractService @Autowired constructor(
+ private val modAttribPathService: ModAttribPathService,
+ private val iconsService: IconsService,
+) {
+
+ val log = LoggerFactory.getLogger(BuildingAddonRgdExtractService::class.java)
+
+ data class AddonTexts(
+ val name: String?,
+ val description: String?,
+ )
+
+ fun extractToAddonEntity(
+ fileName: String,
+ modDictionary: Map,
+ addonRgdData: List,
+ modFolderData: String,
+ building: Building,
+ mod: Mod,
+ ): BuildingAddon {
+
+ val addon = BuildingAddon()
+
+ val nameAndDescription = getAddonNameAndDescription(addonRgdData, modDictionary)
+ addon.name = nameAndDescription.name
+ addon.description = nameAndDescription.description
+ addon.building = building
+ addon.filename = fileName
+
+ val buildCost = getAddonCost(addonRgdData)
+ addon.addonCostRequisition = buildCost.requisition
+ addon.addonCostPower = buildCost.power
+ addon.addonCostPopulation = buildCost.population
+ addon.addonCostFaith = buildCost.faith
+ addon.addonCostSouls = buildCost.souls
+ addon.addonCostTime = buildCost.time
+
+ val addonIcon = convertIconAndReturnPath(addonRgdData, modFolderData, mod.name)
+ addon.icon = addonIcon
+
+
+ return addon
+ }
+
+
+ private fun getAddonCost(addonData: List): BuildCost {
+
+ val cost = addonData.getRgdTableByName("time_cost")
+
+ val costResources = cost?.getRgdTableByName("cost")
+
+ return BuildCost(
+ costResources?.getDoubleByName("requisition"),
+ costResources?.getDoubleByName("power"),
+ costResources?.getDoubleByName("population"),
+ costResources?.getDoubleByName("faith"),
+ costResources?.getDoubleByName("souls"),
+ cost?.getDoubleByName("time_seconds")?.toInt()
+ )
+ }
+
+ private fun getAddonNameAndDescription(addonData: List, modDictionary: Map): AddonTexts {
+ val uiInfo = addonData.getRgdTableByName("ui_info")
+
+ val nameRef = uiInfo?.getStringByName("screen_name_id")?.replace("$", "")
+ val name = nameRef?.let { try{modDictionary[it.toInt()]} catch (e: Exception) { null } }
+
+ val descriptionRefs = uiInfo?.getRgdTableByName("help_text_list")
+ ?.map{(it.value as String).replace("$", "")}
+ ?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
+ ?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
+
+ val description = try {
+ descriptionRefs?.map { modDictionary[it.toInt()] }?.joinToString ( "\n" )
+ } catch(e:Exception) {
+ log.warn("Error parsing ui description", e)
+ null
+ }
+
+ return AddonTexts(name, description)
+ }
+
+
+ private fun convertIconAndReturnPath(buildingData: List, modFolderData: String, modName: String?): String? {
+ 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) }
+ }
+
+}
diff --git a/src/main/kotlin/com/dowstats/service/w40k/BuildingRgdExtractService.kt b/src/main/kotlin/com/dowstats/service/w40k/BuildingRgdExtractService.kt
new file mode 100644
index 0000000..162b9a4
--- /dev/null
+++ b/src/main/kotlin/com/dowstats/service/w40k/BuildingRgdExtractService.kt
@@ -0,0 +1,239 @@
+package com.dowstats.service.w40k
+
+import com.dowstats.data.dto.BuildCost
+import com.dowstats.data.dto.BuildingDataToSave
+import com.dowstats.data.entities.*
+import com.dowstats.data.rgd.RgdData
+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
+import java.io.File
+import java.nio.file.Path
+import kotlin.io.path.exists
+
+@Service
+class BuildingRgdExtractService @Autowired constructor(
+ private val modAttribPathService: ModAttribPathService,
+ private val iconsService: IconsService,
+ private val addonRgdExtractService: BuildingAddonRgdExtractService,
+) {
+
+ val log = LoggerFactory.getLogger(BuildingRgdExtractService::class.java)
+
+ data class HealthData(
+ val hitpoints: Double?,
+ val regeneration: Double?,
+ val maxRepaires: Int?,
+ )
+
+ data class WeaponsData(
+ val hardpoint: Int,
+ val hardpointOrder: Int,
+ val weaponFilename: String,
+ )
+
+ data class BuildingTexts(
+ val name: String?,
+ val description: String?,
+ )
+
+ fun extractToBuildingEntity(
+ fileName: String,
+ modDictionary: Map,
+ buildingData: List,
+ weapons: Set,
+ race: String,
+ modFolderData: String,
+ mod: Mod,
+ races: List,
+ armorTypes: List,
+ addonsRgdData: Map>,
+ ): BuildingDataToSave {
+
+ val building = Building()
+ val race = races.find { it.id == race } ?: throw Exception("Cant get race $race")
+
+ building.race = race
+ building.armorType = getBuildingArmourType(buildingData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
+ building.armorType2 = getBuildingArmourType(buildingData, armorTypes, "type_armour_2")
+
+ val nameAndDescription = getBuildingNameAndDescription(buildingData, modDictionary)
+ building.name = nameAndDescription.name
+ building.description = nameAndDescription.description
+ building.filename = fileName
+
+ val buildCost = getBuildCost(buildingData)
+ building.buildCostRequisition = buildCost.requisition
+ building.buildCostPower = buildCost.power
+ building.buildCostPopulation = buildCost.population
+ building.buildCostFaith = buildCost.faith
+ building.buildCostSouls = buildCost.souls
+ building.buildCostTime = buildCost.time
+
+
+
+ val healthData = getHealthData(buildingData)
+ building.health = healthData.hitpoints?.toInt()
+ building.healthRegeneration = healthData.regeneration
+
+ building.sightRadius = getSightRadius(buildingData)?.toInt()
+ building.detectRadius = getDetectRadius(buildingData)?.toInt()
+
+ building.repairMax = healthData.maxRepaires
+
+ val addons = getAddons(buildingData)
+ building.addons = addons?.mapNotNull {addonFileName ->
+ val addonRgdData = addonsRgdData[addonFileName]
+ if(addonRgdData != null){
+ addonRgdExtractService.extractToAddonEntity(addonFileName, modDictionary, addonRgdData, modFolderData, building, mod)
+ } else {
+ log.warn("Can't find addon $addonFileName")
+ null
+ }
+ }?.toMutableSet()
+
+ val buildingIcon = convertIconAndReturnPath(buildingData, modFolderData, mod.name)
+ building.icon = buildingIcon
+
+ val buildingWeapons = getBuildingWeapon(buildingData)?.mapNotNull { weaponData ->
+ weapons.find {
+ it.filename == weaponData.weaponFilename + ".rgd"
+ }.also {
+ it?.hardpoint = weaponData.hardpoint
+ it?.hardpointOrder = weaponData.hardpointOrder
+ }
+ }.orEmpty().toSet()
+
+ building.modId = mod.id
+
+ return BuildingDataToSave(building, buildingWeapons)
+ }
+
+
+ private fun getBuildingArmourType(
+ unitData: List,
+ armorTypes: Iterable,
+ armorTypeTableName: String
+ ): ArmorType? {
+ val armorType = unitData.getRgdTableByName("type_ext")
+ ?.getRgdTableByName(armorTypeTableName)
+ ?.getStringByName("\$REF")
+
+ return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") }
+ }
+
+ private fun getBuildCost(buildingData: List): BuildCost {
+
+ val cost = buildingData.getRgdTableByName("cost_ext")
+ ?.getRgdTableByName("time_cost")
+
+ val costResources = cost?.getRgdTableByName("cost")
+
+ return BuildCost(
+ costResources?.getDoubleByName("requisition"),
+ costResources?.getDoubleByName("power"),
+ costResources?.getDoubleByName("population"),
+ costResources?.getDoubleByName("faith"),
+ costResources?.getDoubleByName("souls"),
+ cost?.getDoubleByName("time_seconds")?.toInt()
+ )
+ }
+
+ private fun getBuildingNameAndDescription(buildingData: List, modDictionary: Map): BuildingTexts {
+ val uiInfo = buildingData.getRgdTableByName("ui_ext")
+ ?.getRgdTableByName("ui_info")
+
+ val nameRef = uiInfo?.getStringByName("screen_name_id")?.replace("$", "")
+ val name = nameRef?.let { try{modDictionary[it.toInt()]} catch (e: Exception) { null } }
+
+ val descriptionRefs = uiInfo?.getRgdTableByName("help_text_list")
+ ?.map{(it.value as String).replace("$", "")}
+ ?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
+ ?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
+
+ val description = try {
+ descriptionRefs?.map { modDictionary[it.toInt()] }?.joinToString ( "\n" )
+ } catch(e:Exception) {
+ log.warn("Error parsing ui description", e)
+ null
+ }
+
+ return BuildingTexts(name, description)
+ }
+
+ private fun getHealthData(buildingData: List): HealthData {
+ val healthExt = buildingData.getRgdTableByName("health_ext")
+ return HealthData(
+ healthExt?.getDoubleByName("hitpoints"),
+ healthExt?.getDoubleByName("regeneration_rate"),
+ healthExt?.getIntByName("max_repairers")
+ )
+ }
+
+
+ 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 getBuildingWeapon(buildingData: List?): List? = buildingData
+ ?.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 != "") {
+ WeaponsData(hardpointValue,
+ weapon.name.replace("weapon_", "").toInt(),
+ it.replace("weapon\\", "").replace(".lua", ""))
+ } else null
+ }
+ }
+ }
+ } else null
+ }?.flatten()
+
+
+ private fun convertIconAndReturnPath(buildingData: List, modFolderData: String, modName: String?): String? {
+ val iconPathInMod = buildingData
+ .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) }
+ }
+
+
+ private fun getAddons(buildingData: List): List? = buildingData
+ .getRgdTableByName("addon_ext")
+ ?.getRgdTableByName("addons")
+ ?.mapNotNull { addon ->
+ if (addon.value != "") {
+ addon.value.toString()
+ .replace("addons\\", "")
+ .replace(".lua", "")
+ .plus(".rgd")
+ } else null
+ }
+}
+
diff --git a/src/main/kotlin/com/dowstats/service/w40k/IconsService.kt b/src/main/kotlin/com/dowstats/service/w40k/IconsService.kt
index b9101c3..e5eb2f7 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/IconsService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/IconsService.kt
@@ -20,7 +20,7 @@ class IconsService @Autowired constructor(
* @param pathToTgaIcon - путь до иконки
* @return путь до сконвертированной иконки
*/
- fun convertTgaToJpegImage(iconPathInMod: String, pathToTgaIcon: String): String? {
+ fun convertTgaToJpegImage(iconPathInMod: String, pathToTgaIcon: String, modName: String? = null): String? {
try{
val image: BufferedImage = try {
ImageIO.read(File(pathToTgaIcon))
@@ -28,7 +28,10 @@ class IconsService @Autowired constructor(
ImageIO.read(File(pathToTgaIcon.lowercase()))
}
- val pathToSave = "${storageConfig.iconsStorage.replace("/", File.separator)}${File.separator}${iconPathInMod.replace("\\", File.separator)}.png"
+ val modFolder = modName?.let { "${File.separator}$modName" } ?: ""
+
+ val pathToSave = "${storageConfig.iconsStorage.replace("/", File.separator)}$modFolder" +
+ "${File.separator}${iconPathInMod.replace("\\", File.separator)}.png"
val directoryToSave = File(pathToSave.split(File.separator).dropLast(1).joinToString (File.separator))
if(!directoryToSave.exists()) directoryToSave.mkdirs()
@@ -45,8 +48,8 @@ class IconsService @Autowired constructor(
}
- fun returnIcon(raceIconFolder: String, iconName: String): ByteArray? {
- val pathToIcon = "${storageConfig.iconsStorage.replace("/", File.separator)}${File.separator}$raceIconFolder${File.separator}$iconName"
+ fun returnIcon(modName: String, raceIconFolder: String, iconName: String): ByteArray? {
+ val pathToIcon = "${storageConfig.iconsStorage.replace("/", File.separator)}${File.separator}$modName${File.separator}$raceIconFolder${File.separator}$iconName"
return File(pathToIcon).readBytes()
}
diff --git a/src/main/kotlin/com/dowstats/service/w40k/ModAttribPathService.kt b/src/main/kotlin/com/dowstats/service/w40k/ModAttribPathService.kt
index aaebe08..195f1ef 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/ModAttribPathService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/ModAttribPathService.kt
@@ -21,6 +21,9 @@ class ModAttribPathService @Autowired constructor(
fun getWeaponAttribsPath(modFolderData: String): String =
"$modFolderData${File.separator}attrib${File.separator}weapon"
+ fun getAddonAttribsPath(modFolderData: String): String =
+ "$modFolderData${File.separator}attrib${File.separator}addons"
+
fun getSbpsAttribsFolderPath(modFolderData: String): String =
"$modFolderData${File.separator}attrib${File.separator}sbps${File.separator}races${File.separator}"
diff --git a/src/main/kotlin/com/dowstats/service/w40k/ModParserService.kt b/src/main/kotlin/com/dowstats/service/w40k/ModParserService.kt
index 4b1223c..269d9dd 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/ModParserService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/ModParserService.kt
@@ -24,6 +24,8 @@ class ModParserService @Autowired constructor(
val weaponRgdExtractService: WeaponRgdExtractService,
val weaponRepository: WeaponRepository,
val modAttribPathService: ModAttribPathService,
+ val buildingRepository: BuildingRepository,
+ val buildingExtractService: BuildingRgdExtractService,
) {
val defaultDictionary: MutableMap = mutableMapOf()
@@ -53,8 +55,6 @@ class ModParserService @Autowired constructor(
log.info("Start parse mod files ${mod.technicalName}:${mod.version}")
- val modId = mod.id!!
-
val modFolderData = modAttribPathService.getModFolderData(mod.technicalName!!, mod.version!!)
val racesList = Files.walk(Path(modAttribPathService.getSbpsAttribsFolderPath(modFolderData)), 1)
.toList()
@@ -62,10 +62,9 @@ class ModParserService @Autowired constructor(
.filter { Files.isDirectory(it) }.map { it.name }.toList()
racesList.forEach{
- unitRepository.deleteAllByModIdAndRaceId(modId, it)
+ unitRepository.deleteAllByModIdAndRaceId(mod.id!!, it)
}
-
val modDictionary: MutableMap = mutableMapOf()
log.info("Extract dictionaries from $modFolderData")
@@ -73,7 +72,10 @@ class ModParserService @Autowired constructor(
it.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 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
}
@@ -81,25 +83,20 @@ class ModParserService @Autowired constructor(
val enrichedModDictionary = modDictionary + defaultDictionary
- val weapons = saveWeapons(modFolderData, modId, enrichedModDictionary)
- saveUnits(modFolderData, weapons, racesList, modId, enrichedModDictionary)
+ val weapons = saveWeapons(modFolderData, mod, enrichedModDictionary)
+ saveUnits(modFolderData, weapons, racesList, mod, enrichedModDictionary)
+
+ saveBuildings(modFolderData, weapons, racesList, mod, enrichedModDictionary)
}
- fun saveUnits(modFolderData: String, weapons: Set, racesList: List, modId: Long, modDictionary: Map) {
+ fun saveUnits(modFolderData: String, weapons: Set, racesList: List, mod: Mod, modDictionary: Map) {
val races = raceRepository.findAll().toList()
val armorTypes = armorTypeRepository.findAll().toList()
racesList.forEach { raceFolder ->
- if( raceRepository.findById(raceFolder) == null){
- val race = Race().also { it.id = raceFolder; it.name = raceFolder }
- raceRepository.save(race)
- }
-
- println(raceFolder)
-
val classicRgdDataSquads =
rgdParserService.parseFolderToRgdFiles(modAttribPathService.getSbpsAttribsPath(modAttribPathService.pathToWanilaData, raceFolder))
val modRgdDataSquads =
@@ -136,7 +133,7 @@ class ModParserService @Autowired constructor(
weapons,
raceFolder,
modFolderData,
- modId,
+ mod,
races,
armorTypes,
modUnitsFull
@@ -148,18 +145,18 @@ 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
- it.hardpoint = weapon.hardpoint
- it.hardpointOrder = weapon.hardpointOrder
+ it.weapon = weapon.weapon
it.unitWeaponKey = UnitWeaponKey().also {
it.unitId = unit.id
- it.weaponId = weapon.id
+ it.weaponId = weapon.weapon?.id
+ it.hardpoint = weapon.hardpoint
+ it.hardpointOrder = weapon.hardpointOrder
}
}
- }.toMutableSet()
+ }?.toMutableSet()
unitRepository.save(unit)
@@ -171,11 +168,12 @@ class ModParserService @Autowired constructor(
SergeantWeapon().also {
it.sergeant = sergeant
it.weapon = weapon
- it.hardpoint = weapon.hardpoint
- it.hardpointOrder = weapon.hardpointOrder
+
it.sergeantWeaponKey = SergeantWeaponKey().also { swk ->
swk.sergeantId = sergeant.id
swk.weaponId = weapon.id
+ swk.hardpoint = weapon.hardpoint
+ swk.hardpointOrder = weapon.hardpointOrder
}
}
}.toMutableSet()
@@ -188,7 +186,80 @@ class ModParserService @Autowired constructor(
}
}
- fun saveWeapons(modFolderData: String, modId: Long, modDictionary: Map): Set {
+ fun saveBuildings(modFolderData: String,
+ weapons: Set,
+ racesList: List,
+ mod: Mod, modDictionary: Map) {
+
+ val races = raceRepository.findAll().toList()
+ val armorTypes = armorTypeRepository.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
+
+ modStructuresFull.forEach { structure ->
+ val structureDataToSave =
+ try {
+ buildingExtractService.extractToBuildingEntity(
+ structure.key,
+ modDictionary,
+ structure.value,
+ weapons,
+ raceFolder,
+ 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.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
+ }
+ }
+ }.toMutableSet()
+ buildingRepository.save(building)
+
+ } catch (e: Exception) {
+ throw e
+ }
+ }
+
+
+ }
+ }
+
+ fun getAddonsRgdData(modFolderData: String): Map> {
+
+ val classicAddons = rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/addons")
+
+ val modAddons =
+ rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAddonAttribsPath(modFolderData))
+
+ return classicAddons + modAddons
+ }
+
+ fun saveWeapons(modFolderData: String, mod: Mod, modDictionary: Map): Set {
val classicWeapons = rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}/attrib/weapon")
@@ -198,7 +269,7 @@ class ModParserService @Autowired constructor(
val allWeapons = classicWeapons + modWeapons
val weaponsToSave = allWeapons.mapNotNull {
- weaponRgdExtractService.extractToWeaponEntity(it.key, it.value, modId, modFolderData, modDictionary)
+ weaponRgdExtractService.extractToWeaponEntity(it.key, it.value, mod, modFolderData, modDictionary)
}
return try {
diff --git a/src/main/kotlin/com/dowstats/service/w40k/SergantRgdExtractService.kt b/src/main/kotlin/com/dowstats/service/w40k/SergantRgdExtractService.kt
index bfaf514..794a3bf 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/SergantRgdExtractService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/SergantRgdExtractService.kt
@@ -48,6 +48,7 @@ class SergeantRgdExtractService @Autowired constructor(
fun extractToSergeantEntity(
fileName: String,
modDictionary: Map,
+ mod: Mod,
sergeantData: List,
weapons: Set,
modFolderData: String,
@@ -84,7 +85,7 @@ class SergeantRgdExtractService @Autowired constructor(
sergeant.mass = massData.mass
sergeant.upTime = massData.upTime
- val unitIcon = convertSergeantIconAndReturnPath(sergeantData, modFolderData)
+ val unitIcon = convertSergeantIconAndReturnPath(sergeantData, modFolderData, mod.name)
sergeant.icon = unitIcon
val sergeantWeapons = getSergeantWeapons(sergeantData)?.mapNotNull { weaponData ->
@@ -183,7 +184,7 @@ class SergeantRgdExtractService @Autowired constructor(
} else null
}?.flatten()
- private fun convertSergeantIconAndReturnPath(sergeantData: List, modFolderData: String): String? {
+ private fun convertSergeantIconAndReturnPath(sergeantData: List, modFolderData: String, modName: String?): String? {
val iconPathInMod = sergeantData
.getRgdTableByName("ui_ext")
?.getRgdTableByName("ui_info")
@@ -197,7 +198,7 @@ class SergeantRgdExtractService @Autowired constructor(
modAttribPathService.getIconPath(modAttribPathService.pathToWanilaData, it)
}
- return tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it) }
+ return tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
}
diff --git a/src/main/kotlin/com/dowstats/service/w40k/UnitRgdExtractService.kt b/src/main/kotlin/com/dowstats/service/w40k/UnitRgdExtractService.kt
index cfd7660..6c7d88e 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/UnitRgdExtractService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/UnitRgdExtractService.kt
@@ -2,6 +2,7 @@ package com.dowstats.service.w40k
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.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
@@ -36,12 +37,6 @@ class UnitRgdExtractService @Autowired constructor(
val regeneration: Double?,
)
- data class WeaponsData(
- val hardpoint: Int,
- val hardpointOrder: Int,
- val weaponFilename: String,
- )
-
data class SergeantData(
val filePath: String,
val cost: BuildCost,
@@ -65,7 +60,7 @@ class UnitRgdExtractService @Autowired constructor(
weapons: Set,
race: String,
modFolderData: String,
- modId: Long,
+ mod: Mod,
races: List,
armorTypes: List,
modUnitsFull: Map>,
@@ -131,10 +126,11 @@ class UnitRgdExtractService @Autowired constructor(
val sergeantsEntities: List>>? = sergeantsData.first?.mapNotNull { sergeantData ->
val sergeantFile = sergeantData.filePath.split("\\").last().replace(".lua", ".rgd")
val sergeantRgdData = modUnitsFull[sergeantFile]
- if(sergeantRgdData != null){
+ if (sergeantRgdData != null) {
sergeantRgdExtractService.extractToSergeantEntity(
sergeantFile,
modDictionary,
+ mod,
sergeantRgdData,
weapons,
modFolderData,
@@ -146,19 +142,12 @@ class UnitRgdExtractService @Autowired constructor(
unit.maxSergeants = sergeantsData.second
- val unitIcon = convertIconAndReturnPath(squadData, modFolderData)
+ val unitIcon = convertIconAndReturnPath(mod.name, squadData, modFolderData)
unit.icon = unitIcon
- val unitWeapons = getUnitWeapons(unitData)?.mapNotNull { weaponData ->
- weapons.find {
- it.filename == weaponData.weaponFilename + ".rgd"
- }.also {
- it?.hardpoint = weaponData.hardpoint
- it?.hardpointOrder = weaponData.hardpointOrder
- }
- }.orEmpty().toSet()
+ val unitWeapons = getUnitWeapons(unitData, weapons)
- unit.modId = modId
+ unit.modId = mod.id
return UnitDataToSave(unit, unitWeapons, sergeantsEntities)
}
@@ -212,16 +201,28 @@ class UnitRgdExtractService @Autowired constructor(
val nameRef = uiInfo?.getStringByName("screen_name_id")?.replace("$", "")
- val name = nameRef?.let { try{modDictionary[it.toInt()]} catch (e: Exception) { null } }
+ val name = nameRef?.let {
+ try {
+ modDictionary[it.toInt()]
+ } catch (e: Exception) {
+ null
+ }
+ }
val descriptionRefs = uiInfo?.getRgdTableByName("help_text_list")
- ?.map{(it.value as String).replace("$", "")}
- ?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
- ?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
+ ?.map { (it.value as String).replace("$", "") }
+ ?.filter { it != "0" && it != "tables\\text_table.lua" && it != "" }
+ ?.sortedBy {
+ try {
+ it.toInt()
+ } catch (e: Exception) {
+ 0
+ }
+ }
val description = try {
- descriptionRefs?.map { modDictionary[it.toInt()] }?.joinToString ( "\n" )
- } catch(e:Exception) {
+ descriptionRefs?.map { modDictionary[it.toInt()] }?.joinToString("\n")
+ } catch (e: Exception) {
log.warn("Error parsing ui description", e)
null
}
@@ -321,20 +322,34 @@ class UnitRgdExtractService @Autowired constructor(
private fun getReinforceTime(reinforceData: List?): Int? = reinforceData
?.getIntByName("time_seconds")
- private fun getUnitWeapons(reinforceData: List?): List? = reinforceData
+ private fun getUnitWeapons(reinforceData: List?, weapons: Set): List? = reinforceData
?.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 != "") {
- WeaponsData(hardpointValue,
- weapon.name.replace("weapon_", "").toInt(),
- it.replace("weapon\\", "").replace(".lua", ""))
+ val weaponFileName = it.replace("weapon\\", "").replace(".lua", ".rgd")
+ val weaponEntity = weapons.find {
+ it.filename == weaponFileName
+ }
+ if(weaponEntity == null){
+ log.warn("Can't find weapon $weaponFileName")
+ null
+ } else {
+ WeaponsData(
+ hardpointValue,
+ weapon.name.replace("weapon_", "").toInt(),
+ weaponEntity
+ )
+ }
} else null
}
}
@@ -350,34 +365,35 @@ class UnitRgdExtractService @Autowired constructor(
val maxSergeants = squadTable?.getIntByName("max_leaders")
val sergeantsData = squadTable?.mapNotNull { sergeantData ->
- if (sergeantData.name.contains("leader_")) {
- val sergeantRgdTable = sergeantData.value as List
+ if (sergeantData.name.contains("leader_")) {
+ val sergeantRgdTable = sergeantData.value as List
- val sergeantLeaderFilePath = sergeantRgdTable.getRgdTableByName("leader")?.getStringByName("type")
+ val sergeantLeaderFilePath = sergeantRgdTable.getRgdTableByName("leader")?.getStringByName("type")
- if(sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else {
+ if (sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else {
- val cost = sergeantRgdTable.getRgdTableByName("cost_time")
- val costResources = cost?.getRgdTableByName("cost")
+ val cost = sergeantRgdTable.getRgdTableByName("cost_time")
+ val costResources = cost?.getRgdTableByName("cost")
- SergeantData(sergeantLeaderFilePath,
- BuildCost(
- costResources?.getDoubleByName("requisition"),
- costResources?.getDoubleByName("power"),
- costResources?.getDoubleByName("population"),
- costResources?.getDoubleByName("faith"),
- costResources?.getDoubleByName("souls"),
- cost?.getIntByName("time_seconds"),
- )
+ SergeantData(
+ sergeantLeaderFilePath,
+ BuildCost(
+ costResources?.getDoubleByName("requisition"),
+ costResources?.getDoubleByName("power"),
+ costResources?.getDoubleByName("population"),
+ costResources?.getDoubleByName("faith"),
+ costResources?.getDoubleByName("souls"),
+ cost?.getIntByName("time_seconds"),
)
- }
- } else null
- }
+ )
+ }
+ } else null
+ }
return Pair(sergeantsData, maxSergeants)
}
- private fun convertIconAndReturnPath(squadData: List, modFolderData: String): String? {
+ private fun convertIconAndReturnPath(modName: String?, squadData: List, modFolderData: String): String? {
val iconPathInMod = squadData
.getRgdTableByName("squad_ui_ext")
?.getRgdTableByName("ui_info")
@@ -387,11 +403,11 @@ class UnitRgdExtractService @Autowired constructor(
val tgaIconPath = iconPathInMod?.let {
val modIcon = modAttribPathService.getIconPath(modFolderData, it)
- if(Path.of(modIcon).exists()) modIcon else
+ if (Path.of(modIcon).exists()) modIcon else
modAttribPathService.getIconPath(modAttribPathService.pathToWanilaData, it)
}
- return tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it) }
+ return tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
}
diff --git a/src/main/kotlin/com/dowstats/service/w40k/WeaponRgdExtractService.kt b/src/main/kotlin/com/dowstats/service/w40k/WeaponRgdExtractService.kt
index c83e5be..a70d3bc 100644
--- a/src/main/kotlin/com/dowstats/service/w40k/WeaponRgdExtractService.kt
+++ b/src/main/kotlin/com/dowstats/service/w40k/WeaponRgdExtractService.kt
@@ -1,6 +1,7 @@
package com.dowstats.service.w40k
import com.dowstats.data.entities.ArmorType
+import com.dowstats.data.entities.Mod
import com.dowstats.data.entities.Weapon
import com.dowstats.data.entities.WeaponArmorPiercing
import com.dowstats.data.repositories.ArmorTypeRepository
@@ -32,9 +33,12 @@ class WeaponRgdExtractService @Autowired constructor(
val seconds: Int?
)
- data class ArmourDamage(
+ data class AreaEffectData(
val minDamage: Double,
val maxDamage: Double,
+ val damageRadius: Double,
+ val throwForceMin: Double,
+ val throwForceMax: Double,
val minDamageValue: Double,
val moraleDamage: Double,
val armourPiercing: List,
@@ -47,14 +51,14 @@ class WeaponRgdExtractService @Autowired constructor(
val haveEquipButton: Boolean
)
- fun extractToWeaponEntity(weaponFileName: String, weaponData: List, modId: Long, modFolderData: String, modDictionary: Map): Weapon? {
+ fun extractToWeaponEntity(weaponFileName: String, weaponData: List, mod: Mod, modFolderData: String, modDictionary: Map): Weapon? {
val armorTypes = armorTypeRepository.findAll().toSet()
val weapon = Weapon()
weapon.filename = weaponFileName
- val weaponUiData = getWeaponNameAndDescription(weaponData, modDictionary, modFolderData)
+ val weaponUiData = getWeaponNameAndDescription(weaponData, modDictionary, modFolderData, mod)
weapon.name = weaponUiData.name
weapon.icon = weaponUiData.iconPath
weapon.description = weaponUiData.description
@@ -67,21 +71,26 @@ class WeaponRgdExtractService @Autowired constructor(
weapon.costTimeSeconds = cost.seconds ?: 0
weapon.accuracy = weaponData.getDoubleByName("accuracy")
weapon.accuracyReductionMoving = weaponData.getDoubleByName("accuracy_reduction_when_moving")
+ weapon.minRange = weaponData.getDoubleByName("min_range")
weapon.maxRange = weaponData.getDoubleByName("max_range")
+
weapon.reloadTime = weaponData.getDoubleByName("reload_time")
weapon.setupTime = weaponData.getDoubleByName("setup_time")
weapon.isMeleeWeapon = weaponData.getBooleanByName("melee_weapon") ?: false
- weapon.canAttackAir = weaponData.getBooleanByName("can_attack_air_units") ?: false
- weapon.canAttackGround = weaponData.getBooleanByName("can_attack_ground_units") ?: false
+ weapon.canAttackAir = weaponData.getBooleanByName("can_attack_air_units") ?: true
+ weapon.canAttackGround = weaponData.getBooleanByName("can_attack_ground_units") ?: true
- val armourDamage = getArmourDamage(weaponData, armorTypes, weapon)
- weapon.minDamageValue = armourDamage.minDamageValue
- weapon.minDamage = armourDamage.minDamage
- weapon.maxDamage = armourDamage.maxDamage
- weapon.moraleDamage = armourDamage.moraleDamage
- weapon.weaponPiercings = armourDamage.armourPiercing
- weapon.modId = modId
+ val areaEffectData = getAreaEffectData(weaponData, armorTypes, weapon)
+ weapon.minDamageValue = areaEffectData.minDamageValue
+ weapon.minDamage = areaEffectData.minDamage
+ weapon.maxDamage = areaEffectData.maxDamage
+ weapon.throwForceMin = areaEffectData.throwForceMin
+ weapon.throwForceMax = areaEffectData.throwForceMax
+ weapon.moraleDamage = areaEffectData.moraleDamage
+ weapon.weaponPiercings = areaEffectData.armourPiercing
+ weapon.damageRadius = areaEffectData.damageRadius
+ weapon.modId = mod.id
return if(weapon.minDamage == 0.0 && weapon.maxDamage == 0.0 && weapon.moraleDamage == 0.0){
null
@@ -97,8 +106,15 @@ class WeaponRgdExtractService @Autowired constructor(
return BuildCost(requisition, power, costTime)
}
- private fun getArmourDamage(weaponData: List, armorTypes: Set, thisWeapon: Weapon): ArmourDamage {
- val armourDamage = weaponData.getRgdTableByName("area_effect")
+ private fun getAreaEffectData(weaponData: List, armorTypes: Set, thisWeapon: Weapon): AreaEffectData {
+ val areaEffect = weaponData.getRgdTableByName("area_effect")
+ val damageRadius = areaEffect?.getRgdTableByName("area_effect_information")?.getDoubleByName("radius") ?: 0.0
+
+ val throwData = areaEffect?.getRgdTableByName("throw_data")
+ val forceMin = throwData?.getDoubleByName("force_min") ?: 0.0
+ val forceMax = throwData?.getDoubleByName("force_max") ?: 0.0
+
+ val armourDamage = areaEffect
?.getRgdTableByName("weapon_damage")
?.getRgdTableByName("armour_damage")!!
val minDamage = armourDamage.getDoubleByName("min_damage")!!
@@ -106,6 +122,7 @@ class WeaponRgdExtractService @Autowired constructor(
val minDamageValue = armourDamage.getDoubleByName("min_damage_value")!!
val moraleDamage = armourDamage.getDoubleByName("morale_damage")!!
+
val defaultArmourPiercing = armourDamage.getDoubleByName("armour_piercing")
val weaponDmgMap: Map =
armourDamage.getRgdTableByName("armour_piercing_types")!!.mapNotNull { armour_piercing ->
@@ -125,11 +142,11 @@ class WeaponRgdExtractService @Autowired constructor(
weaponArmourPiercing
}
- return ArmourDamage(minDamage, maxDamage, minDamageValue, moraleDamage, armoursPiercing)
+ return AreaEffectData(minDamage, maxDamage, damageRadius, forceMin, forceMax, minDamageValue, moraleDamage, armoursPiercing)
}
- private fun getWeaponNameAndDescription(weaponData: List, modDictionary: Map, modFolderData: String): WeaponUiInfo {
+ private fun getWeaponNameAndDescription(weaponData: List, modDictionary: Map, modFolderData: String, mod: Mod): WeaponUiInfo {
val weaponUiInfo = weaponData.getRgdTableByName("ui_info")
val nameRef = weaponUiInfo?.getStringByName("screen_name_id")?.replace("$", "")
@@ -156,7 +173,7 @@ class WeaponRgdExtractService @Autowired constructor(
if(Path.of(modIcon).exists()) modIcon else
modAttribPathService.getIconPath(modAttribPathService.pathToWanilaData, it)
}
- tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPath, it) }
+ tgaIconPath?.let { iconsService.convertTgaToJpegImage(iconPath, it, mod.name) }
} catch (e: Exception) {
log.error("Error parsing ui icon path", e)
null
diff --git a/src/main/resources/db/0.0.1/schema/addon_modifiers.json b/src/main/resources/db/0.0.1/schema/addon_modifiers.json
new file mode 100644
index 0000000..2a86160
--- /dev/null
+++ b/src/main/resources/db/0.0.1/schema/addon_modifiers.json
@@ -0,0 +1,63 @@
+{
+ "databaseChangeLog": [
+ {
+ "changeSet": {
+ "id": "Add buildings table",
+ "author": "anibus",
+ "changes": [
+ {
+ "createTable": {
+ "tableName": "addon_modifiers",
+ "columns": [
+ {
+ "column": {
+ "name": "id",
+ "type": "int",
+ "autoIncrement": true,
+ "constraints": {
+ "primaryKey": true,
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "references",
+ "type": "varchar(255)"
+ }
+ },{
+ "column": {
+ "name": "usage_type",
+ "type": "varchar(255)"
+ }
+ },{
+ "column": {
+ "name": "value",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "addon_id",
+ "baseTableName": "addon_modifiers",
+ "constraintName": "fk_building_addons_addon_modifiers",
+ "referencedColumnNames": "id",
+ "referencedTableName": "building_addons"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/db/0.0.1/schema/addon_requires.json b/src/main/resources/db/0.0.1/schema/addon_requires.json
new file mode 100644
index 0000000..4fe22a2
--- /dev/null
+++ b/src/main/resources/db/0.0.1/schema/addon_requires.json
@@ -0,0 +1,63 @@
+{
+ "databaseChangeLog": [
+ {
+ "changeSet": {
+ "id": "Add buildings table",
+ "author": "anibus",
+ "changes": [
+ {
+ "createTable": {
+ "tableName": "addon_requires",
+ "columns": [
+ {
+ "column": {
+ "name": "id",
+ "type": "int",
+ "autoIncrement": true,
+ "constraints": {
+ "primaryKey": true,
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "references",
+ "type": "varchar(255)"
+ }
+ },{
+ "column": {
+ "name": "value",
+ "type": "varchar(255)"
+ }
+ },{
+ "column": {
+ "name": "replace_when_done",
+ "type": "boolean"
+ }
+ },{
+ "column": {
+ "name": "addon_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "addon_id",
+ "baseTableName": "addon_requires",
+ "constraintName": "fk_building_addons_addon_requires",
+ "referencedColumnNames": "id",
+ "referencedTableName": "building_addons"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/db/0.0.1/schema/building_addons.json b/src/main/resources/db/0.0.1/schema/building_addons.json
new file mode 100644
index 0000000..d380367
--- /dev/null
+++ b/src/main/resources/db/0.0.1/schema/building_addons.json
@@ -0,0 +1,104 @@
+{
+ "databaseChangeLog": [
+ {
+ "changeSet": {
+ "id": "Add buildings table",
+ "author": "anibus",
+ "changes": [
+ {
+ "createTable": {
+ "tableName": "building_addons",
+ "columns": [
+ {
+ "column": {
+ "name": "id",
+ "type": "int",
+ "autoIncrement": true,
+ "constraints": {
+ "primaryKey": true,
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "filename",
+ "type": "varchar(255)",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "name",
+ "type": "varchar(255)"
+ }
+ },{
+ "column": {
+ "name": "description",
+ "type": "varchar(5000)"
+ }
+ },
+ {
+ "column": {
+ "name": "addon_cost_requisition",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_cost_power",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_cost_population",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_cost_faith",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_cost_souls",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "addon_cost_time",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "icon",
+ "type": "varchar(128)"
+ }
+ },
+ {
+ "column": {
+ "name": "building_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "building_id",
+ "baseTableName": "building_addons",
+ "constraintName": "fk_building_addons_buildings",
+ "referencedColumnNames": "id",
+ "referencedTableName": "buildings"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/db/0.0.1/schema/buildings.json b/src/main/resources/db/0.0.1/schema/buildings.json
new file mode 100644
index 0000000..8748a3f
--- /dev/null
+++ b/src/main/resources/db/0.0.1/schema/buildings.json
@@ -0,0 +1,170 @@
+{
+ "databaseChangeLog": [
+ {
+ "changeSet": {
+ "id": "Add buildings table",
+ "author": "anibus",
+ "changes": [
+ {
+ "createTable": {
+ "tableName": "buildings",
+ "columns": [
+ {
+ "column": {
+ "name": "id",
+ "type": "int",
+ "autoIncrement": true,
+ "constraints": {
+ "primaryKey": true,
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "filename",
+ "type": "varchar(255)",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "name",
+ "type": "varchar(4096)"
+ }
+ },{
+ "column": {
+ "name": "description",
+ "type": "varchar(5000)"
+ }
+ },
+ {
+ "column": {
+ "name": "build_cost_requisition",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "build_cost_power",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "build_cost_population",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "build_cost_faith",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "build_cost_souls",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "build_cost_time",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "health",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "health_regeneration",
+ "type": "number"
+ }
+ },{
+ "column": {
+ "name": "race_id",
+ "type": "varchar(50)",
+ "constraints": {
+ "nullable":false
+ }
+ }
+ },{
+ "column": {
+ "name": "armour_type_id",
+ "type": "varchar(50)",
+ "constraints": {
+ "nullable":false
+ }
+ }
+ },{
+ "column": {
+ "name": "armour_type_2_id",
+ "type": "varchar(50)"
+ }
+ },{
+ "column": {
+ "name": "sight_radius",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "detect_radius",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "repair_max",
+ "type": "int"
+ }
+ },{
+ "column": {
+ "name": "mod_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "icon",
+ "type": "varchar(128)"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "race_id",
+ "baseTableName": "buildings",
+ "constraintName": "fk_buildings_races",
+ "referencedColumnNames": "id",
+ "referencedTableName": "races"
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "armour_type_id",
+ "baseTableName": "buildings",
+ "constraintName": "fk_buildings_armor_types",
+ "referencedColumnNames": "id",
+ "referencedTableName": "armor_types"
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "mod_id",
+ "baseTableName": "buildings",
+ "constraintName": "fk_buildings_mods",
+ "referencedColumnNames": "id",
+ "referencedTableName": "mods"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/db/0.0.1/schema/buildings_weapons.json b/src/main/resources/db/0.0.1/schema/buildings_weapons.json
new file mode 100644
index 0000000..b34ef9d
--- /dev/null
+++ b/src/main/resources/db/0.0.1/schema/buildings_weapons.json
@@ -0,0 +1,82 @@
+{
+ "databaseChangeLog": [
+ {
+ "changeSet": {
+ "id": "Add buildings_weapons table",
+ "author": "anibus",
+ "changes": [
+ {
+ "createTable": {
+ "tableName": "buildings_weapons",
+ "columns": [
+ {
+ "column": {
+ "name": "building_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "weapon_id",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "hardpoint",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },
+ {
+ "column": {
+ "name": "hardpoint_order",
+ "type": "int",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "addUniqueConstraint": {
+ "columnNames": "building_id, weapon_id, hardpoint, hardpoint_order",
+ "constraintName": "uc_building_weapon_hardpoint_hardpoint_order",
+ "tableName": "buildings_weapons"
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "building_id",
+ "baseTableName": "buildings_weapons",
+ "constraintName": "fk_buildings_buildings_weapons",
+ "referencedColumnNames": "id",
+ "referencedTableName": "buildings"
+ }
+ },
+ {
+ "addForeignKeyConstraint":
+ {
+ "baseColumnNames": "weapon_id",
+ "baseTableName": "buildings_weapons",
+ "constraintName": "fk_weapons_buildings_weapons",
+ "referencedColumnNames": "id",
+ "referencedTableName": "weapons"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/db/0.0.1/schema/sergants_weapons.json b/src/main/resources/db/0.0.1/schema/sergants_weapons.json
index e853607..6111a91 100644
--- a/src/main/resources/db/0.0.1/schema/sergants_weapons.json
+++ b/src/main/resources/db/0.0.1/schema/sergants_weapons.json
@@ -50,8 +50,8 @@
},
{
"addUniqueConstraint": {
- "columnNames": "sergeant_id, weapon_id",
- "constraintName": "uc_sergeant_weapon",
+ "columnNames": "sergeant_id, weapon_id, hardpoint, hardpoint_order",
+ "constraintName": "uc_sergeant_weapon_hardpoint_hardpoint_order",
"tableName": "sergeants_weapons"
}
},
diff --git a/src/main/resources/db/0.0.1/schema/units.json b/src/main/resources/db/0.0.1/schema/units.json
index 5bc032c..de6eccb 100644
--- a/src/main/resources/db/0.0.1/schema/units.json
+++ b/src/main/resources/db/0.0.1/schema/units.json
@@ -31,7 +31,7 @@
},{
"column": {
"name": "name",
- "type": "varchar(255)"
+ "type": "varchar(4096)"
}
},{
"column": {
@@ -215,12 +215,6 @@
"name": "icon",
"type": "varchar(128)"
}
- },
- {
- "column": {
- "name": "hotkey_name",
- "type": "varchar(64)"
- }
}
]
}
diff --git a/src/main/resources/db/0.0.1/schema/units_weapons.json b/src/main/resources/db/0.0.1/schema/units_weapons.json
index 7d952ce..35d27d5 100644
--- a/src/main/resources/db/0.0.1/schema/units_weapons.json
+++ b/src/main/resources/db/0.0.1/schema/units_weapons.json
@@ -50,8 +50,8 @@
},
{
"addUniqueConstraint": {
- "columnNames": "unit_id, weapon_id",
- "constraintName": "uc_unit_weapon",
+ "columnNames": "unit_id, weapon_id, hardpoint, hardpoint_order",
+ "constraintName": "uc_unit_weapon_hardpoint_hardpoint_order",
"tableName": "units_weapons"
}
},
diff --git a/src/main/resources/db/0.0.1/schema/weapons.json b/src/main/resources/db/0.0.1/schema/weapons.json
index 31745bf..7bb1d8d 100644
--- a/src/main/resources/db/0.0.1/schema/weapons.json
+++ b/src/main/resources/db/0.0.1/schema/weapons.json
@@ -31,7 +31,7 @@
},{
"column": {
"name": "name",
- "type": "varchar(255)"
+ "type": "varchar(4096)"
}
},{
"column": {
@@ -94,6 +94,11 @@
"nullable": false
}
}
+ },{
+ "column": {
+ "name": "min_range",
+ "type": "number"
+ }
},{
"column": {
"name": "max_range",
@@ -131,6 +136,30 @@
"nullable": false
}
}
+ },{
+ "column": {
+ "name": "damage_radius",
+ "type": "number",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "throw_force_min",
+ "type": "number",
+ "constraints": {
+ "nullable": false
+ }
+ }
+ },{
+ "column": {
+ "name": "throw_force_max",
+ "type": "number",
+ "constraints": {
+ "nullable": false
+ }
+ }
},
{
"column": {
diff --git a/src/main/resources/db/changelog-master.json b/src/main/resources/db/changelog-master.json
index e20e599..b6d3c23 100644
--- a/src/main/resources/db/changelog-master.json
+++ b/src/main/resources/db/changelog-master.json
@@ -21,10 +21,30 @@
"include": {
"file": "db/0.0.1/schema/sergants.json"
}
+ },{
+ "include": {
+ "file": "db/0.0.1/schema/buildings.json"
+ }
},{
"include": {
"file": "db/0.0.1/schema/weapons.json"
}
+ },{
+ "include": {
+ "file": "db/0.0.1/schema/buildings_weapons.json"
+ }
+ },{
+ "include": {
+ "file": "db/0.0.1/schema/building_addons.json"
+ }
+ },{
+ "include": {
+ "file": "db/0.0.1/schema/addon_modifiers.json"
+ }
+ },{
+ "include": {
+ "file": "db/0.0.1/schema/addon_requires.json"
+ }
},{
"include": {
"file": "db/0.0.1/schema/units_weapons.json"
diff --git a/src/test/kotlin/com/example/dowstats/service/w40k/BuildingRgdExtractServiceTest.kt b/src/test/kotlin/com/example/dowstats/service/w40k/BuildingRgdExtractServiceTest.kt
new file mode 100644
index 0000000..bf94000
--- /dev/null
+++ b/src/test/kotlin/com/example/dowstats/service/w40k/BuildingRgdExtractServiceTest.kt
@@ -0,0 +1,50 @@
+import com.dowstats.configuration.StorageConfig
+import com.dowstats.data.entities.ArmorType
+import com.dowstats.data.entities.Mod
+import com.dowstats.data.entities.Race
+import com.dowstats.service.w40k.*
+import io.mockk.junit5.MockKExtension
+import io.mockk.mockk
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import java.io.DataInputStream
+import java.io.File
+import kotlin.test.assertEquals
+
+@ExtendWith(MockKExtension::class)
+class BuildingRgdExtractServiceTest {
+
+ val modAttribPathService = ModAttribPathService(StorageConfig())
+ val iconService = mockk()
+ val addonRgdExtractService = mockk()
+
+ val buildingRgdExtractService = BuildingRgdExtractService(modAttribPathService, iconService, addonRgdExtractService)
+
+ val rgdService = RgdParserService()
+
+ @Test
+ fun `Should correct parse building`() {
+
+ val buildingRgdFile = File("src/test/resources/rgd/waagh_banner/ork_waagh_banner.rgd")
+
+ val rgdData = rgdService.parseRgdFileStream(DataInputStream(buildingRgdFile.inputStream()))
+
+ val res = buildingRgdExtractService.extractToBuildingEntity("Waagh banner", emptyMap(), rgdData, emptySet(), "orks", "/modFolder", Mod(), listOf(
+ Race().also {
+ it.id = "orks"
+ it.name = "Орки"
+ }
+ ), listOf(
+ ArmorType().also {
+ it.id = "building_low"
+ it.name = "Лёгкие здания"
+ }
+ ),
+ emptyMap())
+
+ assertEquals(170.toDouble(), res.building.buildCostRequisition)
+ assertEquals(emptySet(), res.buildingWeapons)
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
new file mode 100644
index 0000000..48fd808
--- /dev/null
+++ b/src/test/resources/application.properties
@@ -0,0 +1,27 @@
+spring.datasource.url=jdbc:postgresql://localhost:5433/wiki-db
+spring.datasource.username=wiki-application
+spring.datasource.password=hQXmQLJkKXdbIgBx
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.liquibase.change-log=classpath:db/changelog-master.json
+
+spring.servlet.multipart.enabled=true
+spring.servlet.multipart.max-file-size=500MB
+spring.servlet.multipart.max-request-size=500MB
+
+spring.jpa.database=postgresql
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
+spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
+
+
+hibernate.jdbc.batch_size = 1000
+
+server.port=8082
+
+steam.api.key=1DDFA1A35D907D5F5C20418A8D1885C1
+
+storage.key=3F82D04CC274897B2552E8798F82F7A1
+storage.take-last=2
+storage.mod-storage=/home/cnb/mods
+storage.wanila-storage=/home/cnb/wanila
+storage.icons-storage=/home/cnb/icons
diff --git a/src/test/resources/rgd/waagh_banner/ork_waagh_banner.rgd b/src/test/resources/rgd/waagh_banner/ork_waagh_banner.rgd
new file mode 100644
index 0000000..52e47d3
Binary files /dev/null and b/src/test/resources/rgd/waagh_banner/ork_waagh_banner.rgd differ