Add researches support

This commit is contained in:
Anibus 2025-08-18 12:20:24 +03:00
parent 74bfa790a7
commit 2d941bd29c
69 changed files with 2034 additions and 720 deletions

View File

@ -15,7 +15,7 @@
<description>Down of war wiki</description> <description>Down of war wiki</description>
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
<kotlin.version>1.8.22</kotlin.version> <kotlin.version>2.2.0</kotlin.version>
<spring.version>3.3.1</spring.version> <spring.version>3.3.1</spring.version>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -4,4 +4,13 @@ object Metadata {
const val USER_ROLE = "USER" const val USER_ROLE = "USER"
object Requirements {
val REFERENCE_REQUIREMENT_POP = "requirements\\required_total_pop.lua"
val REFERENCE_REQUIREMENT_ADDON = "requirements\\local_required_addon.lua"
val REFERENCE_GLOBAL_REQUIREMENT_ADDON = "requirements\\global_required_addon.lua"
val REFERENCE_REQUIREMENT_STRUCTURE_EITHER = "requirements\\required_structure_either.lua"
val REFERENCE_REQUIREMENT_STRUCTURE = "requirements\\required_structure.lua"
val REFERENCE_REQUIREMENT_RESEARCH = "requirements\\required_research.lua"
}
} }

View File

@ -0,0 +1,13 @@
package com.dowstats.data.dto
data class AreaEffect(
val minDamage: Double,
val maxDamage: Double,
val damageRadius: Double,
val throwForceMin: Double,
val throwForceMax: Double,
val minDamageValue: Double,
val moraleDamage: Double,
val weaponDmgMap: Map<String, Double>,
val defaultArmorPiercing: Double,
)

View File

@ -0,0 +1,7 @@
package com.dowstats.data.dto
data class ResourceIncome(
val faith: Double?,
val power: Double?,
val requisition: Double?,
)

View File

@ -0,0 +1,7 @@
package com.dowstats.data.dto
data class UiInfo (
val name: String?,
val description: String?,
val iconPath: String?,
)

View File

@ -0,0 +1,48 @@
package com.dowstats.data.dto.controllers
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 EntityCompressDto(
val name: String?,
val icon: String?,
val id: Long,
val filename: String?,
)
object EntityCompressDtoObject {
fun Sergeant.compressDto(): EntityCompressDto =
EntityCompressDto(
this.name,
this.icon,
this.id!!,
this.filename
)
fun DowUnit.compressDto(): EntityCompressDto =
EntityCompressDto(
this.name,
this.icon,
this.id!!,
this.filenameSquad + ";" + this.filenameUnit
)
fun Building.compressDto(): EntityCompressDto =
EntityCompressDto(
this.name,
this.icon,
this.id!!,
this.filename
)
fun Weapon.compressDto(): EntityCompressDto =
EntityCompressDto(
this.name,
this.icon,
this.id!!,
this.filename
)
}

View File

@ -1,8 +1,9 @@
package com.dowstats.data.dto.controllers.building package com.dowstats.data.dto.controllers
data class AddonModifierDto ( data class ModifierDto (
val id: Long, val id: Long,
val reference: String?, val reference: String?,
val usageType: String?, val usageType: String?,
val value: Double?, val value: Double?,
val target: String? = null,
) )

View File

@ -0,0 +1,20 @@
package com.dowstats.data.dto.controllers
import com.dowstats.data.dto.controllers.building.BuildingAddonShortDto
import com.dowstats.data.dto.controllers.building.BuildingShortDto
import com.dowstats.data.dto.controllers.research.ResearchShortDto
data class RequirementDto (
val requirementBuildings: Set<BuildingShortDto>,
val requirementBuildingsEither: Set<BuildingShortDto>,
val requirementResearches: Set<RequirementResearchDto>,
val requireAddon: BuildingAddonShortDto?,
val requirementsGlobalAddons: Set<BuildingAddonShortDto>?,
val requiredTotalPop: Int?,
val replaceWhenDone: Boolean = false,
)
data class RequirementResearchDto(
val researchShortDto: ResearchShortDto,
val researchMustNotBeComplete: Boolean,
)

View File

@ -1,5 +1,7 @@
package com.dowstats.data.dto.controllers package com.dowstats.data.dto.controllers
import com.dowstats.data.dto.controllers.research.ResearchShortDto
data class SergeantDto( data class SergeantDto(
val id: Long?, val id: Long?,
@ -27,4 +29,5 @@ data class SergeantDto(
val detectRadius: Int?, val detectRadius: Int?,
val icon: String?, val icon: String?,
val weapons: List<WeaponSlotDto>?, val weapons: List<WeaponSlotDto>?,
val affectedResearches: Set<ResearchShortDto>,
) )

View File

@ -0,0 +1,9 @@
package com.dowstats.data.dto.controllers
data class SergeantUnitShortDto(
val id: Long?,
val filename: String,
val name: String?,
val icon: String?,
val unit: EntityCompressDto
)

View File

@ -1,5 +1,7 @@
package com.dowstats.data.dto.controllers package com.dowstats.data.dto.controllers
import com.dowstats.data.dto.controllers.research.ResearchShortDto
data class UnitFullDto( data class UnitFullDto(
val id: Long, val id: Long,
@ -35,17 +37,19 @@ data class UnitFullDto(
val moveSpeed: Int?, val moveSpeed: Int?,
val sightRadius: Int?, val sightRadius: Int?,
val detectRadius: Int?, val detectRadius: Int?,
val reinforceCostRequisition: Int?, val reinforceCostRequisition: Double?,
val reinforceCostPower: Int?, val reinforceCostPower: Double?,
val reinforceCostPopulation: Int?, val reinforceCostPopulation: Double?,
val reinforceCostFaith: Int?, val reinforceCostFaith: Double?,
val reinforceCostSouls: Int?, val reinforceCostSouls: Double?,
val reinforceTime: Int?, val reinforceTime: Int?,
val repairMax: Int?, val repairMax: Int?,
val repairSpeed: Int?, val repairSpeed: Int?,
val repairCostPercent: Int?,
val maxSergeants: Int?, val maxSergeants: Int?,
val icon: String?, val icon: String?,
val modId: Long, val modId: Long,
val sergeants: Set<SergeantDto>?, val sergeants: Set<SergeantDto>?,
val weapons: Set<WeaponSlotDto>?, val weapons: Set<WeaponSlotDto>?,
val affectedResearches: Set<ResearchShortDto>,
) )

View File

@ -1,12 +1,14 @@
package com.dowstats.data.dto.controllers package com.dowstats.data.dto.controllers
import com.dowstats.data.dto.controllers.research.ResearchShortDto
data class WeaponDto( data class WeaponDto(
val id: Long?, val id: Long?,
val filename: String, val filename: String,
val name: String?, val name: String?,
val description: String?, val description: String?,
val costRequisition: Int?, val costRequisition: Double?,
val costPower: Int?, val costPower: Double?,
val costTimeSeconds: Int?, val costTimeSeconds: Int?,
val accuracy: Double?, val accuracy: Double?,
val reloadTime: Double?, val reloadTime: Double?,
@ -27,5 +29,6 @@ data class WeaponDto(
val icon: String?, val icon: String?,
val haveEquipButton: Boolean, val haveEquipButton: Boolean,
val modId: Long?, val modId: Long?,
val weaponArmorPiercing: List<WeaponArmorPiercingDto> val weaponArmorPiercing: List<WeaponArmorPiercingDto>,
val affectedResearches: Set<ResearchShortDto>,
) )

View File

@ -0,0 +1,10 @@
package com.dowstats.data.dto.controllers
data class WeaponUnitShortDto(
val id: Long?,
val filename: String,
val name: String?,
val units: Set<EntityCompressDto>,
val sergeants: Set<SergeantUnitShortDto>,
val buildings: Set<EntityCompressDto>
)

View File

@ -1,10 +0,0 @@
package com.dowstats.data.dto.controllers.building
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

@ -1,5 +1,8 @@
package com.dowstats.data.dto.controllers.building package com.dowstats.data.dto.controllers.building
import com.dowstats.data.dto.controllers.ModifierDto
import com.dowstats.data.dto.controllers.RequirementDto
data class BuildingAddonDto( data class BuildingAddonDto(
val id: Long?, val id: Long?,
val name: String?, val name: String?,
@ -11,8 +14,8 @@ data class BuildingAddonDto(
val addonCostFaith: Double?, val addonCostFaith: Double?,
val addonCostSouls: Double?, val addonCostSouls: Double?,
val addonCostTime: Int?, val addonCostTime: Int?,
val addonModifiers: Set<AddonModifierDto>, val addonModifiers: Set<ModifierDto>,
val addonRequirement: AddonRequirementDto?, val addonRequirement: RequirementDto?,
val icon: String?, val icon: String?,
) )

View File

@ -4,7 +4,8 @@ import com.dowstats.data.dto.controllers.ArmorTypeDto
import com.dowstats.data.dto.controllers.RaceDto import com.dowstats.data.dto.controllers.RaceDto
import com.dowstats.data.dto.controllers.UnitShortDto import com.dowstats.data.dto.controllers.UnitShortDto
import com.dowstats.data.dto.controllers.WeaponSlotDto import com.dowstats.data.dto.controllers.WeaponSlotDto
import com.dowstats.data.entities.DowUnit import com.dowstats.data.dto.controllers.research.ResearchDto
import com.dowstats.data.dto.controllers.research.ResearchShortDto
data class BuildingFullDto( data class BuildingFullDto(
var id: Long?, var id: Long?,
@ -32,6 +33,8 @@ data class BuildingFullDto(
var icon: String?, var icon: String?,
var modId: Long?, var modId: Long?,
var addons: Set<BuildingAddonDto>?, var addons: Set<BuildingAddonDto>?,
var researches: List<ResearchDto>?,
val units: Set<UnitShortDto>, val units: Set<UnitShortDto>,
val weapons: Set<WeaponSlotDto>?, val weapons: Set<WeaponSlotDto>?,
val affectedResearches: Set<ResearchShortDto>,
) )

View File

@ -12,6 +12,7 @@ data class BuildingShortDto(
val name: String, val name: String,
val icon: String, val icon: String,
val id: Long, val id: Long,
val filename: String,
val units: Set<UnitShortDto>, val units: Set<UnitShortDto>,
val armourTypeName: String, val armourTypeName: String,
val canDetect: Boolean, val canDetect: Boolean,

View File

@ -0,0 +1,24 @@
package com.dowstats.data.dto.controllers.research
import com.dowstats.data.dto.controllers.*
data class ResearchDto(
val id: Long?,
val name: String?,
val description: String?,
val filename: String?,
val costRequisition: Double?,
val costPower: Double?,
val costFaith: Double?,
val costSouls: Double?,
val costTime: Int?,
val modifiers: List<ModifierDto>,
val requirements: RequirementDto?,
val affectedUnits: Set<EntityCompressDto>,
val affectedSergeants: Set<SergeantUnitShortDto>,
val affectedBuildings: Set<EntityCompressDto>,
val affectedWeapons: Set<WeaponUnitShortDto>,
val icon: String?,
val modId: Long?,
)

View File

@ -0,0 +1,8 @@
package com.dowstats.data.dto.controllers.research
data class ResearchShortDto(
val id: Long?,
val name: String?,
val icon: String?,
val buildingId: Long?,
)

View File

@ -1,32 +0,0 @@
package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.building.AddonRequirementDto
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.*
@Entity
@Table(name = "addon_requirements")
class AddonRequirements {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
@ManyToOne
@JoinColumn(name = "addon_id", nullable = false)
@JsonIgnore
var addon: BuildingAddon? = null
var reference: String? = null
var replaceWhenDone: Boolean = false
var value: String? = null
companion object {
val REFERENCE_REQUIREMENT_POP = "requirements\\required_total_pop.lua"
val REFERENCE_REQUIREMENT_ADDON = "requirements\\local_required_addon.lua"
val REFERENCE_GLOBAL_REQUIREMENT_ADDON = "requirements\\global_required_addon.lua"
val REFERENCE_REQUIREMENT_STRUCTURE_EITHER = "requirements\\required_structure_either.lua"
val REFERENCE_REQUIREMENT_STRUCTURE = "requirements\\required_structure.lua"
}
}

View File

@ -1,10 +1,12 @@
package com.dowstats.data.entities package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.UnitShortDto
import com.dowstats.data.dto.controllers.building.BuildingAddonDto import com.dowstats.data.dto.controllers.building.BuildingAddonDto
import com.dowstats.data.dto.controllers.building.BuildingFullDto import com.dowstats.data.dto.controllers.building.BuildingFullDto
import com.dowstats.data.dto.controllers.research.ResearchDto
import com.dowstats.data.entities.DowUnitObject.toUnitDto import com.dowstats.data.entities.DowUnitObject.toUnitDto
import com.dowstats.data.entities.Weapon.HardpointPosition import com.dowstats.data.entities.Weapon.HardpointPosition
import com.dowstats.data.entities.addon.BuildingAddon
import com.dowstats.data.entities.research.Research
import jakarta.persistence.* import jakarta.persistence.*
@ -16,7 +18,7 @@ class Building {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null var id: Long? = null
var canBuild: Boolean = true var canBuild: Boolean? = true
@ManyToOne @ManyToOne
@JoinColumn(name = "race_id", nullable = false) @JoinColumn(name = "race_id", nullable = false)
@ -63,10 +65,22 @@ class Building {
inverseJoinColumns = [JoinColumn(name = "unit_id")]) inverseJoinColumns = [JoinColumn(name = "unit_id")])
var units: MutableSet<DowUnit>? = null var units: MutableSet<DowUnit>? = null
@ManyToMany
@JoinTable(name = "buildings_researches",
joinColumns = [JoinColumn(name = "building_id")],
inverseJoinColumns = [JoinColumn(name = "research_id")])
var researches: MutableSet<Research>? = null
@ManyToMany
@JoinTable(name = "researches_affected_buildings",
joinColumns = [JoinColumn(name = "building_id")],
inverseJoinColumns = [JoinColumn(name = "research_id")])
var affectedResearches: MutableSet<Research> = mutableSetOf()
@Transient @Transient
var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf() var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf()
fun toDto(buildingAddons: Set<BuildingAddonDto>?): BuildingFullDto = fun toDto(buildingAddons: Set<BuildingAddonDto>?, researches: List<ResearchDto>?): BuildingFullDto =
BuildingFullDto( BuildingFullDto(
id!!, id!!,
race?.toDto(), race?.toDto(),
@ -93,8 +107,10 @@ class Building {
icon, icon,
modId!!, modId!!,
buildingAddons, buildingAddons,
researches,
units?.toList()?.toUnitDto() ?: emptySet(), units?.toList()?.toUnitDto() ?: emptySet(),
weapons?.map { it.toWeaponSlotDto() }?.toSet(), weapons?.map { it.toWeaponSlotDto() }?.toSet(),
affectedResearches.map { it.toResearchShortDto() }.toSet(),
) )

View File

@ -2,6 +2,7 @@ package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.UnitFullDto import com.dowstats.data.dto.controllers.UnitFullDto
import com.dowstats.data.dto.controllers.UnitShortDto import com.dowstats.data.dto.controllers.UnitShortDto
import com.dowstats.data.entities.research.Research
import jakarta.persistence.* import jakarta.persistence.*
@ -30,7 +31,8 @@ class DowUnit {
var name: String? = null var name: String? = null
var description: String? = null var description: String? = null
var filename: String? = null var filenameSquad: String? = null
var filenameUnit: String? = null
var buildCostRequisition: Double? = null var buildCostRequisition: Double? = null
var buildCostPower: Double? = null var buildCostPower: Double? = null
var buildCostPopulation: Double? = null var buildCostPopulation: Double? = null
@ -57,11 +59,11 @@ class DowUnit {
var moveSpeed: Int? = null var moveSpeed: Int? = null
var sightRadius: Int? = null var sightRadius: Int? = null
var detectRadius: Int? = null var detectRadius: Int? = null
var reinforceCostRequisition: Int? = null var reinforceCostRequisition: Double? = null
var reinforceCostPower: Int? = null var reinforceCostPower: Double? = null
var reinforceCostPopulation: Int? = null var reinforceCostPopulation: Double? = null
var reinforceCostFaith: Int? = null var reinforceCostFaith: Double? = null
var reinforceCostSouls: Int? = null var reinforceCostSouls: Double? = null
var reinforceTime: Int? = null var reinforceTime: Int? = null
var maxSergeants: Int? = null var maxSergeants: Int? = null
var faithIncome: Double? = null var faithIncome: Double? = null
@ -76,6 +78,11 @@ class DowUnit {
@OneToMany(mappedBy = "unit", cascade = [CascadeType.ALL]) @OneToMany(mappedBy = "unit", cascade = [CascadeType.ALL])
var weapons: MutableSet<UnitWeapon>? = null var weapons: MutableSet<UnitWeapon>? = null
@ManyToMany
@JoinTable(name = "researches_affected_units",
joinColumns = [JoinColumn(name = "unit_id")],
inverseJoinColumns = [JoinColumn(name = "research_id")])
var affectedResearches: MutableSet<Research> = mutableSetOf()
fun toDto(): UnitFullDto = fun toDto(): UnitFullDto =
@ -86,7 +93,7 @@ class DowUnit {
armorType2?.toDto(), armorType2?.toDto(),
name, name,
description, description,
filename!!, filenameSquad!!,
buildCostRequisition, buildCostRequisition,
buildCostPower, buildCostPower,
buildCostPopulation, buildCostPopulation,
@ -121,34 +128,24 @@ class DowUnit {
reinforceTime, reinforceTime,
repairMax, repairMax,
repairSpeed, repairSpeed,
repairCostPercent,
maxSergeants, maxSergeants,
icon, icon,
modId!!, modId!!,
sergeants?.map { it.toDto() }?.toSet(), sergeants?.map { it.toDto() }?.toSet(),
weapons?.map { it.toWeaponSlotDto() }?.toSet() weapons?.map { it.toWeaponSlotDto() }?.toSet(),
affectedResearches.map { it.toResearchShortDto() }.toSet(),
) )
} }
object DowUnitObject { object DowUnitObject {
fun List<DowUnit>.toUnitDto(): Set<UnitShortDto> = fun List<DowUnit>.toUnitDto(): Set<UnitShortDto> =
this.mapNotNull { this.mapNotNull {
val name = it.name ?: it.filename val name = it.name ?: it.filenameSquad
val icon = it.icon val icon = it.icon
if (name == null || icon == null) null else UnitShortDto(name, icon, it.id!!, if (name == null || icon == null) null else UnitShortDto(name, icon, it.id!!,
it.armorType?.name!!, it.armorType?.name!!,
(it.detectRadius ?: 0) > 0) (it.detectRadius ?: 0) > 0)
}.toSet() }.toSet()
fun List<DowUnit>.filterCompanyUnits(): List<DowUnit> =
this.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
}
} }

View File

@ -0,0 +1,16 @@
package com.dowstats.data.entities
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.MappedSuperclass
@MappedSuperclass
open class RequirementBase {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
var reference: String? = null
var value: String? = null
}

View File

@ -2,6 +2,7 @@ package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.SergeantDto import com.dowstats.data.dto.controllers.SergeantDto
import com.dowstats.data.entities.Weapon.HardpointPosition import com.dowstats.data.entities.Weapon.HardpointPosition
import com.dowstats.data.entities.research.Research
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.* import jakarta.persistence.*
@ -29,7 +30,7 @@ class Sergeant {
var name: String? = null var name: String? = null
var description: String? = null var description: String? = null
var filename: String? = null var filename: String = ""
var buildCostRequisition: Double? = null var buildCostRequisition: Double? = null
var buildCostPower: Double? = null var buildCostPower: Double? = null
var buildCostPopulation: Double? = null var buildCostPopulation: Double? = null
@ -49,6 +50,8 @@ class Sergeant {
var powerIncome: Double? = null var powerIncome: Double? = null
var requisitionIncome: Double? = null var requisitionIncome: Double? = null
var modId: Long? = null
@Transient @Transient
var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf() var weaponHardpoints: MutableSet<HardpointPosition> = mutableSetOf()
@ -56,6 +59,12 @@ class Sergeant {
@OneToMany(mappedBy = "sergeant", cascade = [CascadeType.ALL]) @OneToMany(mappedBy = "sergeant", cascade = [CascadeType.ALL])
var weapons: MutableSet<SergeantWeapon>? = null var weapons: MutableSet<SergeantWeapon>? = null
@ManyToMany
@JoinTable(name = "researches_affected_sergeants",
joinColumns = [JoinColumn(name = "sergeant_id")],
inverseJoinColumns = [JoinColumn(name = "research_id")])
var affectedResearches: MutableSet<Research> = mutableSetOf()
fun toDto(): SergeantDto = fun toDto(): SergeantDto =
SergeantDto( SergeantDto(
id!!, id!!,
@ -82,6 +91,7 @@ class Sergeant {
sightRadius, sightRadius,
detectRadius, detectRadius,
icon, icon,
weapons?.map { it.toWeaponSlotDto() } weapons?.map { it.toWeaponSlotDto() },
affectedResearches.map { it.toResearchShortDto() }.toSet()
) )
} }

View File

@ -1,6 +1,7 @@
package com.dowstats.data.entities package com.dowstats.data.entities
import com.dowstats.data.dto.controllers.WeaponDto import com.dowstats.data.dto.controllers.WeaponDto
import com.dowstats.data.entities.research.Research
import jakarta.persistence.* import jakarta.persistence.*
@ -14,8 +15,8 @@ class Weapon {
var filename: String? = null var filename: String? = null
var name: String? = null var name: String? = null
var description: String? = null var description: String? = null
var costRequisition: Int? = null var costRequisition: Double? = null
var costPower: Int? = null var costPower: Double? = null
var costTimeSeconds: Int? = null var costTimeSeconds: Int? = null
var accuracy: Double? = null var accuracy: Double? = null
var reloadTime: Double? = null var reloadTime: Double? = null
@ -37,6 +38,21 @@ class Weapon {
var haveEquipButton: Boolean = true var haveEquipButton: Boolean = true
var modId: Long? = null var modId: Long? = null
@ManyToMany
@JoinTable(name = "researches_affected_weapons",
joinColumns = [JoinColumn(name = "weapon_id")],
inverseJoinColumns = [JoinColumn(name = "research_id")])
var affectedResearches: MutableSet<Research> = mutableSetOf()
@OneToMany(mappedBy = "weapon", cascade = [CascadeType.ALL])
var unitsUse: MutableSet<UnitWeapon>? = null
@OneToMany(mappedBy = "weapon", cascade = [CascadeType.ALL])
var sergeantsUse: MutableSet<SergeantWeapon>? = null
@OneToMany(mappedBy = "weapon", cascade = [CascadeType.ALL])
var buildingUse: MutableSet<BuildingWeapon>? = null
// for many-to-many persistance // for many-to-many persistance
data class HardpointPosition(val weaponId: Long, val hardpoint: Int, val order: Int) data class HardpointPosition(val weaponId: Long, val hardpoint: Int, val order: Int)
@ -71,6 +87,7 @@ class Weapon {
icon, icon,
haveEquipButton, haveEquipButton,
modId, modId,
weaponPiercings.map { it.toDto() } weaponPiercings.map { it.toDto() },
affectedResearches.map { it.toResearchShortDto() }.toSet(),
) )
} }

View File

@ -1,6 +1,6 @@
package com.dowstats.data.entities package com.dowstats.data.entities.addon
import com.dowstats.data.dto.controllers.building.AddonModifierDto import com.dowstats.data.dto.controllers.ModifierDto
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.* import jakarta.persistence.*
@ -22,12 +22,12 @@ class AddonModifiers {
var usageType: String? = null var usageType: String? = null
var value: Double? = null var value: Double? = null
fun toDto(): AddonModifierDto { fun toDto(): ModifierDto {
return AddonModifierDto( return ModifierDto(
id = id!!, id = id!!,
reference = reference, reference = reference,
usageType = usageType, usageType = usageType,
value = value value = value,
) )
} }
} }

View File

@ -0,0 +1,17 @@
package com.dowstats.data.entities.addon
import com.dowstats.data.entities.RequirementBase
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.*
@Entity
@Table(name = "addon_requirements")
class AddonRequirements: RequirementBase() {
@ManyToOne
@JoinColumn(name = "addon_id", nullable = false)
@JsonIgnore
var addon: BuildingAddon? = null
var replaceWhenDone: Boolean = false
}

View File

@ -1,8 +1,9 @@
package com.dowstats.data.entities package com.dowstats.data.entities.addon
import com.dowstats.data.dto.controllers.building.AddonRequirementDto import com.dowstats.data.dto.controllers.RequirementDto
import com.dowstats.data.dto.controllers.building.BuildingAddonDto import com.dowstats.data.dto.controllers.building.BuildingAddonDto
import com.dowstats.data.dto.controllers.building.BuildingAddonShortDto import com.dowstats.data.dto.controllers.building.BuildingAddonShortDto
import com.dowstats.data.entities.Building
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.* import jakarta.persistence.*
@ -39,7 +40,7 @@ class BuildingAddon {
var icon: String? = null var icon: String? = null
var modId: Long? = null var modId: Long? = null
fun toDto(addonRequirementDto: AddonRequirementDto?): BuildingAddonDto = BuildingAddonDto( fun toDto(addonRequirementDto: RequirementDto?): BuildingAddonDto = BuildingAddonDto(
id = id, id = id,
name = name, name = name,
description = description, description = description,

View File

@ -0,0 +1,137 @@
package com.dowstats.data.entities.research
import com.dowstats.data.dto.controllers.EntityCompressDtoObject.compressDto
import com.dowstats.data.dto.controllers.ModifierDto
import com.dowstats.data.dto.controllers.RequirementDto
import com.dowstats.data.dto.controllers.SergeantUnitShortDto
import com.dowstats.data.dto.controllers.WeaponUnitShortDto
import com.dowstats.data.dto.controllers.research.ResearchDto
import com.dowstats.data.dto.controllers.research.ResearchShortDto
import com.dowstats.data.entities.Building
import com.dowstats.data.entities.DowUnit
import com.dowstats.data.entities.Sergeant
import com.dowstats.data.entities.Weapon
import jakarta.persistence.*
@Entity
@Table(name = "researches")
class Research {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
var name: String? = null
var description: String? = null
var filename: String? = null
var costRequisition: Double? = null
var costPower: Double? = null
var costFaith: Double? = null
var costSouls: Double? = null
var costTime: Int? = null
var icon: String? = null
var modId: Long? = null
@OneToMany(mappedBy = "research", cascade = [CascadeType.ALL])
var researchModifiers: MutableSet<ResearchModifiers> = mutableSetOf()
@OneToMany(mappedBy = "research", cascade = [CascadeType.ALL])
var addonRequirements: MutableSet<ResearchRequirements> = mutableSetOf()
@ManyToMany
@JoinTable(name = "researches_affected_units",
joinColumns = [JoinColumn(name = "research_id")],
inverseJoinColumns = [JoinColumn(name = "unit_id")])
var affectedUnits: MutableSet<DowUnit> = mutableSetOf()
@ManyToMany
@JoinTable(name = "researches_affected_sergeants",
joinColumns = [JoinColumn(name = "research_id")],
inverseJoinColumns = [JoinColumn(name = "sergeant_id")])
var affectedSergeants: MutableSet<Sergeant> = mutableSetOf()
@ManyToMany
@JoinTable(name = "researches_affected_buildings",
joinColumns = [JoinColumn(name = "research_id")],
inverseJoinColumns = [JoinColumn(name = "building_id")])
var affectedBuildings: MutableSet<Building> = mutableSetOf()
@ManyToMany
@JoinTable(name = "researches_affected_weapons",
joinColumns = [JoinColumn(name = "research_id")],
inverseJoinColumns = [JoinColumn(name = "weapon_id")])
var affectedWeapons: MutableSet<Weapon> = mutableSetOf()
@ManyToMany
@JoinTable(name = "buildings_researches",
joinColumns = [JoinColumn(name = "research_id")],
inverseJoinColumns = [JoinColumn(name = "building_id")])
var buildings: MutableSet<Building>? = null
fun toResearchShortDto(): ResearchShortDto = ResearchShortDto(
id = id!!,
name = name,
icon = icon,
buildings?.firstOrNull()?.id
)
fun toResearchDto(requirements: RequirementDto?): ResearchDto = ResearchDto(
id = id!!,
name = name,
icon = icon,
filename = filename,
description = description,
costRequisition = costRequisition,
costPower = costPower,
costFaith = costFaith,
costSouls = costSouls,
costTime = costTime,
modifiers = researchModifiers.map {
ModifierDto(
it.id!!,
it.reference,
it.usageType,
it.value,
it.target,
)
}.sortedBy { it.target },
requirements = requirements,
affectedUnits = affectedUnits.map { it.compressDto() }.toSet(),
affectedSergeants = affectedSergeants.map {
SergeantUnitShortDto(
it.id!!,
it.filename!!,
it.name,
it.icon,
it.unit!!.compressDto()
)
}.toSet(),
affectedBuildings = affectedBuildings.map { it.compressDto() }.toSet(),
affectedWeapons = affectedWeapons.mapNotNull {
if(it.unitsUse!!.isEmpty() && it.sergeantsUse!!.isEmpty() && it.buildingUse!!.isEmpty()) {
null
} else
WeaponUnitShortDto(
it.id!!,
it.filename!!,
it.name,
it.unitsUse?.mapNotNull {
it.unit?.compressDto()
}?.toSet() ?: emptySet(),
it.sergeantsUse?.map {
SergeantUnitShortDto(
it.sergeant!!.id!!,
it.sergeant!!.filename!!,
it.sergeant!!.name,
it.sergeant!!.icon,
it.sergeant!!.unit!!.compressDto()
)
}?.toSet() ?: emptySet(),
it.buildingUse?.map { it.building!!.compressDto() }?.toSet() ?: emptySet()
)
}.toSet(),
modId = modId,
)
}

View File

@ -0,0 +1,35 @@
package com.dowstats.data.entities.research
import com.dowstats.data.dto.controllers.ModifierDto
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.*
@Entity
@Table(name = "research_modifiers")
class ResearchModifiers {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
@ManyToOne
@JoinColumn(name = "research_id", nullable = false)
@JsonIgnore
var research: Research? = null
var reference: String? = null
var target: String? = null
var usageType: String? = null
var value: Double? = null
fun toDto(): ModifierDto {
return ModifierDto(
id = id!!,
reference = reference,
usageType = usageType,
value = value,
target = target,
)
}
}

View File

@ -0,0 +1,16 @@
package com.dowstats.data.entities.research
import com.dowstats.data.entities.RequirementBase
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.*
@Entity
@Table(name = "research_requirements")
class ResearchRequirements : RequirementBase() {
@ManyToOne
@JoinColumn(name = "research_id", nullable = false)
@JsonIgnore
var research: Research? = null
}

View File

@ -1,10 +1,6 @@
package com.dowstats.data.repositories package com.dowstats.data.repositories
import com.dowstats.data.entities.Building import com.dowstats.data.entities.addon.BuildingAddon
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 import org.springframework.data.repository.CrudRepository
interface AddonRepository : CrudRepository<BuildingAddon, Long>{ interface AddonRepository : CrudRepository<BuildingAddon, Long>{

View File

@ -13,4 +13,10 @@ interface ModRepository : CrudRepository<Mod, Long>{
""", nativeQuery = true) """, nativeQuery = true)
@Modifying @Modifying
fun clearModData(modId: Long) fun clearModData(modId: Long)
@Query("""
CALL delete_campaign_entities(:modId)
""", nativeQuery = true)
@Modifying
fun removeCampaignEntities(modId: Long)
} }

View File

@ -0,0 +1,13 @@
package com.dowstats.data.repositories
import com.dowstats.data.entities.DowUnit
import com.dowstats.data.entities.Weapon
import com.dowstats.data.entities.addon.BuildingAddon
import com.dowstats.data.entities.research.Research
import org.springframework.data.repository.*
interface ResearchRepository : CrudRepository<Research, Long>{
fun deleteAllByModId(modId: Long)
fun findAllByModId(modId: Long): List<Research>
fun findFirstByModIdAndFilename(modId: Long, fileName: String): Research?
}

View File

@ -1,6 +1,9 @@
package com.dowstats.data.repositories package com.dowstats.data.repositories
import com.dowstats.data.entities.Sergeant import com.dowstats.data.entities.Sergeant
import com.dowstats.data.entities.Weapon
import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.CrudRepository
interface SergeantRepository : CrudRepository<Sergeant, Long> interface SergeantRepository : CrudRepository<Sergeant, Long>{
fun findAllByModId(modId: Long): List<Sergeant>
}

View File

@ -18,7 +18,5 @@ interface UnitRepository : CrudRepository<DowUnit, Long> {
""") """)
fun findByModIdAndRace(modId: Long, race: Race?): List<DowUnit> fun findByModIdAndRace(modId: Long, race: Race?): List<DowUnit>
fun findByFilenameAndRaceAndModId(fileName: String, race: Race, modId: Long): DowUnit?
fun deleteAllByModIdAndRaceId(modId: Long, raceId: String) fun deleteAllByModIdAndRaceId(modId: Long, raceId: String)
} }

View File

@ -2,8 +2,10 @@ package com.dowstats.data.repositories
import com.dowstats.data.entities.DowUnit import com.dowstats.data.entities.DowUnit
import com.dowstats.data.entities.Weapon import com.dowstats.data.entities.Weapon
import com.dowstats.data.entities.research.Research
import org.springframework.data.repository.* import org.springframework.data.repository.*
interface WeaponRepository : CrudRepository<Weapon, Long>{ interface WeaponRepository : CrudRepository<Weapon, Long>{
fun deleteAllByModId(modId: Long) fun deleteAllByModId(modId: Long)
fun findAllByModId(modId: Long): List<Weapon>
} }

View File

@ -0,0 +1,44 @@
package com.dowstats.service.datamaps
import com.dowstats.data.dto.controllers.building.BuildingShortDto
import com.dowstats.data.entities.Building
import com.dowstats.data.entities.DowUnitObject.toUnitDto
object CommonMapping {
fun List<Building>.toBuildingShortDto(): List<BuildingShortDto> =
this
.sortedWith(compareBy<Building> { it.units?.isEmpty() }.thenBy{ it.buildCostRequisition?.let { 0 - it } })
.mapNotNull {
val name = it.name ?: it.filename?.replace(".rgd", "")?.replace("_", " ")
val icon = it.icon
if (name == null || icon == null) null else BuildingShortDto(
name, icon, it.id!!,
it.filename!!,
it.units?.toList()?.toUnitDto() ?: emptySet(),
it.armorType?.name!!,
(it.detectRadius ?: 0) > 0
)
}
fun isNotCompanyEntity(fileName: String): Boolean =
!fileName.contains("_dxp3.") &&
!fileName.contains("_dxp3_") &&
!fileName.contains("sp_eldar_") &&
!fileName.contains("_sp_") &&
!fileName.contains("_sp.") &&
!fileName.contains("_nis.") &&
!fileName.contains("_interface_relay.") &&
!fileName.contains("necron_tunnel.rgd") &&
!fileName.contains("_exarch_council.") &&
!fileName.contains("_dark_reapers_base.") &&
!fileName.contains("eldar_deep_strike_building.rgd") &&
!fileName.contains("ork_deep_strike_building.rgd") &&
!fileName.contains("_caravel_ai.rgd") &&
!fileName.contains("sisters_tanktrap_ai.rgd") &&
!fileName.contains("sisters_hq_ktgm.rgd") &&
!fileName.contains("tau_squad_slave_murdered") &&
!fileName.contains("single_player_only") &&
!fileName.contains("space_marine_drop_pod_building.rgd")
}

View File

@ -1,147 +1,68 @@
package com.dowstats.service.datamaps package com.dowstats.service.datamaps
import com.dowstats.data.dto.controllers.building.AddonRequirementDto import com.dowstats.Metadata.Requirements
import com.dowstats.data.dto.controllers.building.BuildingFullDto import com.dowstats.data.dto.controllers.building.BuildingFullDto
import com.dowstats.data.dto.controllers.building.BuildingShortDto
import com.dowstats.data.dto.controllers.building.RaceBuildings import com.dowstats.data.dto.controllers.building.RaceBuildings
import com.dowstats.data.entities.AddonRequirements
import com.dowstats.data.entities.Building import com.dowstats.data.entities.Building
import com.dowstats.data.entities.DowUnitObject.filterCompanyUnits
import com.dowstats.data.entities.DowUnitObject.toUnitDto
import com.dowstats.data.repositories.AddonRepository
import com.dowstats.data.repositories.BuildingRepository import com.dowstats.data.repositories.BuildingRepository
import com.dowstats.data.repositories.RaceRepository import com.dowstats.data.repositories.RaceRepository
import com.dowstats.service.datamaps.CommonMapping.isNotCompanyEntity
import com.dowstats.service.datamaps.CommonMapping.toBuildingShortDto
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class DowBuildingMappingService @Autowired constructor( class DowBuildingMappingService @Autowired constructor(
val buildingRepository: BuildingRepository, val buildingRepository: BuildingRepository,
val addonRepository: AddonRepository, val requirementsMappingComponent: RequirementsMappingComponent,
val raceRepository: RaceRepository val raceRepository: RaceRepository
) { ) {
fun findBuildingsByMod(modId: Long): List<RaceBuildings> { fun findBuildingsByMod(modId: Long): List<RaceBuildings> {
return getAllBuildings(modId).groupBy { it.race }.mapNotNull { raceUnits -> return getAllBuildings(modId).groupBy { it.race }.mapNotNull { raceUnits ->
RaceBuildings(raceUnits.key?.toDto() ?: throw Exception("Race is null"), raceUnits.value.toBuildingDto()) RaceBuildings(raceUnits.key?.toDto() ?: throw Exception("Race is null"), raceUnits.value.toBuildingShortDto())
} }
} }
fun findBuildingsByModAndRace(modId: Long, race: String): RaceBuildings { fun findBuildingsByModAndRace(modId: Long, race: String): RaceBuildings {
val buildings = getAllBuildings(modId, race) val buildings = getAllBuildings(modId, race)
val raceEntity = race.let { raceRepository.findById(race) ?: throw Exception("Race $race not found") } val raceEntity = race.let { raceRepository.findById(race) ?: throw Exception("Race $race not found") }
return RaceBuildings(raceEntity.toDto(), buildings.toBuildingDto()) return RaceBuildings(raceEntity.toDto(), buildings.toBuildingShortDto())
} }
fun mapToDto(building: Building): BuildingFullDto { fun mapToDto(building: Building): BuildingFullDto {
val buildingAddons = building.addons?.map { addon -> val buildingAddons = building.addons?.map { addon ->
val requirements = addon.addonRequirements val replaceWhenDone = addon.addonRequirements?.find { it.reference == Requirements.REFERENCE_REQUIREMENT_ADDON }?.replaceWhenDone ?: false
val requirement = requirementsMappingComponent
val requireCap = .getRequirements(addon.addonRequirements?.toList() ?: emptyList(),
requirements?.find { it.reference == AddonRequirements.REFERENCE_REQUIREMENT_POP }?.value?.toDouble() building.modId!!,
?.toInt() building.addons?.toList() ?: emptyList()
val requirementAddon =
requirements?.find { it.reference == AddonRequirements.REFERENCE_REQUIREMENT_ADDON }?.value?.let {
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 ->
val addonFileName = rgra.value?.split("\\")?.last()?.replace(".lua", ".rgd")
addonFileName?.let {
addonRepository.findFirstByModIdAndFilename(
building.modId!!,
it.split("\\").last().replace(".lua", ".rgd")
)?.toShortDto()
}
}
val requirementBuildings =
requirements?.filter { it.reference == AddonRequirements.REFERENCE_REQUIREMENT_STRUCTURE }?.mapNotNull {
val buildingFileName = it.value?.split("\\")?.last()?.replace(".lua", ".rgd")
buildingFileName?.let {
buildingRepository.findByModIdAndFilename(building.modId!!, buildingFileName)
}
}?.filter { it.id != building.id }?.distinct()?.toBuildingDto()
val requirementBuildingsEither =
requirements?.find { it.reference == AddonRequirements.REFERENCE_REQUIREMENT_STRUCTURE_EITHER }?.let {
it.value?.split(";")?.mapNotNull { bPath ->
buildingRepository.findByModIdAndFilename(
building.modId!!,
bPath.split("\\").last().replace(".lua", ".rgd")
)
}
}?.toBuildingDto()
val addonRequirement = if (requireCap != null ||
requirementAddon != null ||
(requirementBuildings?.size ?: 0) > 0 ||
(requirementBuildingsEither?.size ?: 0) > 0 ||
(requirementAddonGlobal?.size ?: 0) > 0
) {
AddonRequirementDto(
requirementBuildings ?: emptyList(),
requirementBuildingsEither ?: emptyList(),
requirementAddon?.toShortDto(),
replaceWhenDone,
requirementAddonGlobal,
requireCap
) )
} else null
addon.toDto(addonRequirement) addon.toDto(requirement?.copy(
replaceWhenDone = replaceWhenDone,
requirementBuildings = requirement.requirementBuildings.filter { it.id != building.id }.toSet())
)
}?.sortedBy { it.name }?.toSet() }?.sortedBy { it.name }?.toSet()
return building.toDto(buildingAddons) val researches = building.researches?.map {research ->
val requiremens = requirementsMappingComponent
.getRequirements(research.addonRequirements.toList() ?: emptyList(),
building.modId!!,
building.addons?.toList() ?: emptyList()
)
research.toResearchDto(requiremens)
}?.sortedBy { it.name }
return building.toDto(buildingAddons, researches)
} }
private fun List<Building>.toBuildingDto(): List<BuildingShortDto> =
this
.sortedWith(compareBy<Building> { it.units?.isEmpty() }.thenBy{ it.buildCostRequisition?.let { 0 - it } })
.mapNotNull {
val name = it.name ?: it.filename?.replace(".rgd", "")?.replace("_", " ")
val icon = it.icon
if (name == null || icon == null) null else BuildingShortDto(
name, icon, it.id!!,
it.units?.toList()?.filterCompanyUnits()?.toUnitDto() ?: emptySet(),
it.armorType?.name!!,
(it.detectRadius ?: 0) > 0
)
}
private fun getAllBuildings(modId: Long, race: String? = null): List<Building> { private fun getAllBuildings(modId: Long, race: String? = null): List<Building> {
val raceEntity = race?.let { raceRepository.findById(race) ?: throw Exception("Race $race not found") } val raceEntity = race?.let { raceRepository.findById(race) ?: throw Exception("Race $race not found") }
return filterCompanyUnits(buildingRepository.findByModIdAndRace(modId, raceEntity)) return buildingRepository.findByModIdAndRace(modId, raceEntity)
} }
private fun filterCompanyUnits(buildings: List<Building>): List<Building> =
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("_interface_relay.") != true
&& it.filename?.contains("necron_tunnel.rgd") != true
&& it.filename?.contains("_exarch_council.") != true
&& it.filename?.contains("_dark_reapers_base.") != true
&& it.filename?.contains("eldar_deep_strike_building.rgd") != true
&& it.filename?.contains("ork_deep_strike_building.rgd") != true
&& it.filename?.contains("_caravel_ai.rgd") != true
&& it.filename?.contains("sisters_tanktrap_ai.rgd") != true
&& it.filename?.contains("sisters_hq_ktgm.rgd") != true
&& it.filename?.contains("tau_squad_slave_murdered") != true
&& it.filename?.contains("single_player_only") != true
&& it.filename?.contains("space_marine_drop_pod_building.rgd") != true
}
} }

View File

@ -1,9 +1,7 @@
package com.dowstats.service.datamaps package com.dowstats.service.datamaps
import com.dowstats.data.dto.controllers.RaceUnits import com.dowstats.data.dto.controllers.RaceUnits
import com.dowstats.data.dto.controllers.UnitShortDto
import com.dowstats.data.entities.DowUnit import com.dowstats.data.entities.DowUnit
import com.dowstats.data.entities.DowUnitObject.filterCompanyUnits
import com.dowstats.data.entities.DowUnitObject.toUnitDto import com.dowstats.data.entities.DowUnitObject.toUnitDto
import com.dowstats.data.entities.Race import com.dowstats.data.entities.Race
import com.dowstats.data.repositories.RaceRepository import com.dowstats.data.repositories.RaceRepository
@ -40,7 +38,7 @@ class DowUnitMappingService @Autowired constructor(
.toUnitDto() .toUnitDto()
val support = units.filter { val support = units.filter {
it.capInfantry?.let { it == 0 } ?: false && it.reinforceCostPopulation?.let { it == 0 } ?: true && it.capInfantry?.let { it == 0 } ?: false && it.reinforceCostPopulation?.let { it == 0.0 } ?: true &&
it.capSupport?.let { it == 0 } ?: false it.capSupport?.let { it == 0 } ?: false
} }
.toUnitDto() .toUnitDto()
@ -51,7 +49,7 @@ class DowUnitMappingService @Autowired constructor(
private fun getAllUnits(modId: Long, race: String? = null): List<DowUnit> { private fun getAllUnits(modId: Long, race: String? = null): List<DowUnit> {
val raceEntity = race?.let{ raceRepository.findById(race) ?: throw Exception("Race $race not found") } val raceEntity = race?.let{ raceRepository.findById(race) ?: throw Exception("Race $race not found") }
return unitRepository.findByModIdAndRace(modId, raceEntity).filterCompanyUnits() return unitRepository.findByModIdAndRace(modId, raceEntity)
} }
} }

View File

@ -0,0 +1,94 @@
package com.dowstats.service.datamaps
import com.dowstats.Metadata.Requirements
import com.dowstats.data.dto.controllers.RequirementDto
import com.dowstats.data.dto.controllers.RequirementResearchDto
import com.dowstats.data.entities.RequirementBase
import com.dowstats.data.entities.addon.BuildingAddon
import com.dowstats.data.repositories.AddonRepository
import com.dowstats.data.repositories.BuildingRepository
import com.dowstats.data.repositories.ResearchRepository
import com.dowstats.service.datamaps.CommonMapping.toBuildingShortDto
import org.springframework.stereotype.Component
@Component
class RequirementsMappingComponent(
val addonRepository: AddonRepository,
val buildingRepository: BuildingRepository,
val researchRepository: ResearchRepository,
) {
fun getRequirements(requirements : List<RequirementBase>, modId: Long, thisBuildingAddons: List<BuildingAddon> = emptyList()) : RequirementDto? {
val requireCap =
requirements.find { it.reference == Requirements.REFERENCE_REQUIREMENT_POP }?.value?.toDouble()
?.toInt()
val requirementAddon =
requirements.find { it.reference == Requirements.REFERENCE_REQUIREMENT_ADDON }?.value?.let {
thisBuildingAddons.find { addon -> addon.filename == it.split("\\").last().replace(".lua", ".rgd") }
}?.toShortDto()
val requirementAddonGlobal =
requirements.filter { it.reference == Requirements.REFERENCE_GLOBAL_REQUIREMENT_ADDON }
.mapNotNull { rgra ->
val addonFileName = rgra.value?.split("\\")?.last()?.replace(".lua", ".rgd")
addonFileName?.let {
addonRepository.findFirstByModIdAndFilename(
modId,
it.split("\\").last().replace(".lua", ".rgd")
)?.toShortDto()
}
}.toSet()
val requirementBuildings =
requirements.filter { it.reference == Requirements.REFERENCE_REQUIREMENT_STRUCTURE }.mapNotNull {
val buildingFileName = it.value?.split("\\")?.last()?.replace(".lua", ".rgd")
buildingFileName?.let {
buildingRepository.findByModIdAndFilename(modId, buildingFileName)
}
}.distinct().toBuildingShortDto().toSet()
val requirementBuildingsEither =
requirements.find { it.reference == Requirements.REFERENCE_REQUIREMENT_STRUCTURE_EITHER }?.let {
it.value?.split(";")?.mapNotNull { bPath ->
buildingRepository.findByModIdAndFilename(
modId,
bPath.split("\\").last().replace(".lua", ".rgd")
)
}
}?.toBuildingShortDto()?.toSet() ?: emptySet()
val requirementResearches =
requirements.filter { it.reference == Requirements.REFERENCE_REQUIREMENT_RESEARCH }.mapNotNull {
val research = it.value?.split(";")?.first()
val mustNotBeCompleteStr = it.value?.split(";")?.last()
val researchFileName = research?.split("\\")?.last()?.replace(".lua", ".rgd")?.let {
if(it.endsWith(".rgd")) it else "$it.rgd"
}
researchFileName?.let {
researchRepository.findFirstByModIdAndFilename(modId, researchFileName)
}?.toResearchShortDto()?.let {
val mustNotBeComplete = mustNotBeCompleteStr == "true"
RequirementResearchDto(it, mustNotBeComplete)
}
}.toSet()
return if (requireCap != null ||
requirementAddon != null ||
requirementBuildings.isNotEmpty() ||
requirementBuildingsEither.isNotEmpty() ||
requirementResearches.isNotEmpty() ||
requirementAddonGlobal.isNotEmpty()
) {
RequirementDto(
requirementBuildings,
requirementBuildingsEither,
requirementResearches,
requirementAddon,
requirementAddonGlobal,
requireCap
)
} else null
}
}

View File

@ -0,0 +1,107 @@
package com.dowstats.service.postparsing
import com.dowstats.configuration.StorageConfig
import com.dowstats.data.entities.*
import com.dowstats.data.entities.research.Research
import com.dowstats.data.repositories.*
import org.slf4j.LoggerFactory
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
class PostParsingService @Autowired constructor(
val researchRepository: ResearchRepository,
val unitRepository: UnitRepository,
val sergeantRepository: SergeantRepository,
val buildingRepository: BuildingRepository,
val weaponRepository: WeaponRepository,
) {
val log = LoggerFactory.getLogger(PostParsingService::class.java)
fun bindResearch(mod: Mod) {
try {
val modId = mod.id!!
val researches = researchRepository.findAllByModId(modId)
val units = unitRepository.findByModIdAndRace(modId, null)
val sergeants = sergeantRepository.findAllByModId(modId)
val buildings = buildingRepository.findByModIdAndRace(modId, null)
val weapons = weaponRepository.findAllByModId(modId)
bindResearchUnits(units, researches)
bindResearchSergeants(sergeants, researches)
bindResearchBuildings(buildings, researches)
bindResearchWeapons(weapons, researches)
} catch (e: Exception) {
log.warn("Error occurred during bind researches", e)
}
}
private fun bindResearchUnits(units: List<DowUnit>, researches: List<Research>) {
val unitsWithBindings: List<DowUnit> = researches.flatMap { research ->
research.researchModifiers.mapNotNull { modifier ->
units.find {
it.filenameSquad?.replace(".rgd", "") == modifier.target ||
it.filenameUnit?.replace(".rgd", "") == modifier.target
}
?.let {
it.affectedResearches.add(research)
it
}
}
}
unitRepository.saveAll(unitsWithBindings)
log.info("Successfully bind researches to units")
}
private fun bindResearchSergeants(sergeants: List<Sergeant>, researches: List<Research>) {
val sergeantsWithBindings: List<Sergeant> = researches.flatMap { research ->
research.researchModifiers.mapNotNull { modifier ->
sergeants.find { it.filename.replace(".rgd", "") == modifier.target }?.let {
it.affectedResearches.add(research)
it
}
}
}
sergeantRepository.saveAll(sergeantsWithBindings)
log.info("Successfully bind researches to sergeants")
}
private fun bindResearchBuildings(buildings: List<Building>, researches: List<Research>) {
val buildingsWithBindings: List<Building> = researches.flatMap { research ->
research.researchModifiers.mapNotNull { modifier ->
buildings.find { it.filename?.replace(".rgd", "") == modifier.target }?.let {
it.affectedResearches.add(research)
it
}
}
}
buildingRepository.saveAll(buildingsWithBindings)
log.info("Successfully bind researches to buildings")
}
private fun bindResearchWeapons(weapons: List<Weapon>, researches: List<Research>) {
val weaponsWithBindings: List<Weapon> = researches.flatMap { research ->
research.researchModifiers.mapNotNull { modifier ->
weapons.find { it.filename?.replace(".rgd", "") == modifier.target }?.let {
it.affectedResearches.add(research)
it
}
}
}
weaponRepository.saveAll(weaponsWithBindings)
log.info("Successfully bind researches to weapons")
}
}

View File

@ -1,7 +1,9 @@
package com.dowstats.service.w40k package com.dowstats.service.w40k
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.entities.* import com.dowstats.data.entities.*
import com.dowstats.data.entities.addon.AddonModifiers
import com.dowstats.data.entities.addon.AddonRequirements
import com.dowstats.data.entities.addon.BuildingAddon
import com.dowstats.data.rgd.RgdData import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
@ -10,23 +12,13 @@ import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.Path
import kotlin.io.path.exists
@Service @Service
class BuildingAddonRgdExtractService @Autowired constructor( class BuildingAddonRgdExtractService @Autowired constructor(
private val modAttribPathService: ModAttribPathService, private val commonParseRgdService: CommonParseRgdService,
private val iconsService: IconsService,
) { ) {
val log = LoggerFactory.getLogger(BuildingAddonRgdExtractService::class.java) val log = LoggerFactory.getLogger(BuildingAddonRgdExtractService::class.java)
data class AddonTexts(
val name: String?,
val description: String?,
)
fun extractToAddonEntity( fun extractToAddonEntity(
fileName: String, fileName: String,
modDictionary: Map<Int, String>, modDictionary: Map<Int, String>,
@ -37,14 +29,16 @@ class BuildingAddonRgdExtractService @Autowired constructor(
): BuildingAddon { ): BuildingAddon {
val addon = BuildingAddon() val addon = BuildingAddon()
val nameAndDescription = getAddonNameAndDescription(addonRgdData, modDictionary)
addon.name = nameAndDescription.name
addon.description = nameAndDescription.description
addon.building = building addon.building = building
val uiInfoRgd = addonRgdData.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
val uiInfo = commonParseRgdService.getUiInfo(uiInfoRgd, modDictionary, modFolderData, mod, log)
addon.name = uiInfo.name
addon.description = uiInfo.description
addon.icon = uiInfo.iconPath
addon.filename = fileName addon.filename = fileName
val buildCost = getAddonCost(addonRgdData) val buildCost = commonParseRgdService.getBuildCost(addonRgdData.getRgdTableByName("time_cost"))
addon.addonCostRequisition = buildCost.requisition addon.addonCostRequisition = buildCost.requisition
addon.addonCostPower = buildCost.power addon.addonCostPower = buildCost.power
addon.addonCostPopulation = buildCost.population addon.addonCostPopulation = buildCost.population
@ -54,8 +48,6 @@ class BuildingAddonRgdExtractService @Autowired constructor(
addon.addonModifiers = getAddonModifiers(addon, addonRgdData).toMutableSet() addon.addonModifiers = getAddonModifiers(addon, addonRgdData).toMutableSet()
addon.addonRequirements = getAddonRequirements(addon, addonRgdData).toMutableSet() addon.addonRequirements = getAddonRequirements(addon, addonRgdData).toMutableSet()
val addonIcon = convertIconAndReturnPath(addonRgdData, modFolderData, mod.name)
addon.icon = addonIcon
addon.modId = mod.id addon.modId = mod.id
return addon return addon
@ -88,82 +80,11 @@ class BuildingAddonRgdExtractService @Autowired constructor(
it.addon = addon it.addon = addon
it.reference = rTable.getStringByName("\$REF") it.reference = rTable.getStringByName("\$REF")
it.replaceWhenDone = rTable.getBooleanByName("replace_when_done") == true it.replaceWhenDone = rTable.getBooleanByName("replace_when_done") == true
it.value = when(it.reference){ it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, addon.filename!!, log)
AddonRequirements.REFERENCE_REQUIREMENT_POP -> rTable.getDoubleByName("population_required").toString()
AddonRequirements.REFERENCE_REQUIREMENT_ADDON -> rTable.getStringByName("addon_name")
AddonRequirements.REFERENCE_GLOBAL_REQUIREMENT_ADDON -> rTable.getStringByName("global_addon_name")
AddonRequirements.REFERENCE_REQUIREMENT_STRUCTURE_EITHER -> rTable.getStringByName("structure_name_or") + ";" + rTable.getStringByName("structure_name_either")
AddonRequirements.REFERENCE_REQUIREMENT_STRUCTURE -> rTable.getStringByName("structure_name")
else -> null
}
} }
} }
} else null } else null
} ?: emptyList() } ?: emptyList()
} }
private fun getAddonCost(addonData: List<RgdData>): 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<RgdData>, modDictionary: Map<Int, String>): 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<RgdData>,
modFolderData: String,
modName: String?
): String? {
val iconPathInMod = buildingData
.getRgdTableByName("ui_info")
?.getStringByName("icon_name")
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
} }

View File

@ -1,27 +1,21 @@
package com.dowstats.service.w40k package com.dowstats.service.w40k
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.dto.BuildingDataToSave import com.dowstats.data.dto.BuildingDataToSave
import com.dowstats.data.entities.* import com.dowstats.data.entities.*
import com.dowstats.data.repositories.UnitRepository import com.dowstats.data.entities.research.Research
import com.dowstats.data.rgd.RgdData import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getIntByName import com.dowstats.data.rgd.RgdDataUtil.getIntByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import com.dowstats.service.w40k.UnitRgdExtractService.ResourceIncome
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.Path
import kotlin.io.path.exists
@Service @Service
class BuildingRgdExtractService @Autowired constructor( class BuildingRgdExtractService @Autowired constructor(
private val modAttribPathService: ModAttribPathService,
private val iconsService: IconsService,
private val addonRgdExtractService: BuildingAddonRgdExtractService, private val addonRgdExtractService: BuildingAddonRgdExtractService,
private val commonParseRgdService: CommonParseRgdService,
) { ) {
val log = LoggerFactory.getLogger(BuildingRgdExtractService::class.java) val log = LoggerFactory.getLogger(BuildingRgdExtractService::class.java)
@ -39,16 +33,12 @@ class BuildingRgdExtractService @Autowired constructor(
val weaponFilename: String, val weaponFilename: String,
) )
data class BuildingTexts(
val name: String?,
val description: String?,
)
fun extractToBuildingEntity( fun extractToBuildingEntity(
fileName: String, fileName: String,
modDictionary: Map<Int, String>, modDictionary: Map<Int, String>,
buildingData: List<RgdData>, buildingData: List<RgdData>,
weapons: Set<Weapon>, weapons: Set<Weapon>,
researches: Set<Research>,
units: List<DowUnit>, units: List<DowUnit>,
race: Race, race: Race,
modFolderData: String, modFolderData: String,
@ -63,13 +53,16 @@ class BuildingRgdExtractService @Autowired constructor(
building.race = race building.race = race
building.armorType = getBuildingArmourType(buildingData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type") building.armorType = getBuildingArmourType(buildingData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
building.armorType2 = getBuildingArmourType(buildingData, armorTypes, "type_armour_2") building.armorType2 = getBuildingArmourType(buildingData, armorTypes, "type_armour_2")
val nameAndDescription = getBuildingNameAndDescription(buildingData, modDictionary)
building.name = nameAndDescription.name
building.description = nameAndDescription.description
building.filename = fileName building.filename = fileName
val buildCost = getBuildCost(buildingData) val buildingUiInfo = buildingData.getRgdTableByName("ui_ext")
?.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
val buildingUiData = commonParseRgdService.getUiInfo(buildingUiInfo, modDictionary, modFolderData, mod, log)
building.name = buildingUiData.name
building.description = buildingUiData.description
building.icon = buildingUiData.iconPath
val buildCost = commonParseRgdService.getBuildCost(buildingData.getRgdTableByName("cost_ext")?.getRgdTableByName("time_cost"))
building.buildCostRequisition = buildCost.requisition building.buildCostRequisition = buildCost.requisition
building.buildCostPower = buildCost.power building.buildCostPower = buildCost.power
building.buildCostPopulation = buildCost.population building.buildCostPopulation = buildCost.population
@ -77,7 +70,7 @@ class BuildingRgdExtractService @Autowired constructor(
building.buildCostSouls = buildCost.souls building.buildCostSouls = buildCost.souls
building.buildCostTime = buildCost.time building.buildCostTime = buildCost.time
val incomeData = getResource(buildingData) val incomeData = commonParseRgdService.getResourceIncome(buildingData.getRgdTableByName("resource_ext"))
building.faithIncome = incomeData.faith building.faithIncome = incomeData.faith
building.powerIncome = incomeData.power building.powerIncome = incomeData.power
building.requisitionIncome = incomeData.requisition building.requisitionIncome = incomeData.requisition
@ -94,6 +87,7 @@ class BuildingRgdExtractService @Autowired constructor(
building.repairMax = healthData.maxRepaires building.repairMax = healthData.maxRepaires
building.units = getUnits(buildingData, units).toMutableSet() building.units = getUnits(buildingData, units).toMutableSet()
building.researches = getResearches(buildingData, researches).toMutableSet()
val addons = getAddons(buildingData) val addons = getAddons(buildingData)
building.addons = addons?.mapNotNull {addonFileName -> building.addons = addons?.mapNotNull {addonFileName ->
@ -106,8 +100,6 @@ class BuildingRgdExtractService @Autowired constructor(
} }
}?.toMutableSet() }?.toMutableSet()
val buildingIcon = convertIconAndReturnPath(buildingData, modFolderData, mod.name)
building.icon = buildingIcon
val buildingWeapons = getBuildingWeapon(buildingData)?.mapNotNull { weaponData -> val buildingWeapons = getBuildingWeapon(buildingData)?.mapNotNull { weaponData ->
weapons.find { weapons.find {
@ -135,54 +127,6 @@ class BuildingRgdExtractService @Autowired constructor(
return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") } return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") }
} }
private fun getBuildCost(buildingData: List<RgdData>): 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 getResource(buildingData: List<RgdData>): ResourceIncome {
val resourceExt = buildingData.getRgdTableByName("resource_ext")
return ResourceIncome(
resourceExt?.getDoubleByName("faith_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("power_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("requisition_per_second")?.let { it * 10 },
)
}
private fun getBuildingNameAndDescription(buildingData: List<RgdData>, modDictionary: Map<Int, String>): 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<RgdData>): HealthData { private fun getHealthData(buildingData: List<RgdData>): HealthData {
val healthExt = buildingData.getRgdTableByName("health_ext") val healthExt = buildingData.getRgdTableByName("health_ext")
return HealthData( return HealthData(
@ -225,17 +169,6 @@ class BuildingRgdExtractService @Autowired constructor(
}?.flatten() }?.flatten()
private fun convertIconAndReturnPath(buildingData: List<RgdData>, modFolderData: String, modName: String?): String? {
val iconPathInMod = buildingData
.getRgdTableByName("ui_ext")
?.getRgdTableByName("ui_info")
?.getStringByName("icon_name")
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
private fun getAddons(buildingData: List<RgdData>): List<String>? = buildingData private fun getAddons(buildingData: List<RgdData>): List<String>? = buildingData
.getRgdTableByName("addon_ext") .getRgdTableByName("addon_ext")
?.getRgdTableByName("addons") ?.getRgdTableByName("addons")
@ -254,10 +187,19 @@ class BuildingRgdExtractService @Autowired constructor(
?.mapNotNull { unit -> ?.mapNotNull { unit ->
if (unit.name.contains("squad_")) { if (unit.name.contains("squad_")) {
val fileName = unit.value.toString().split("\\").last().replace(".lua", ".rgd") val fileName = unit.value.toString().split("\\").last().replace(".lua", ".rgd")
units.find { it.filename == fileName } units.find { it.filenameSquad == fileName }
} else null } else null
} ?: emptyList() } ?: emptyList()
} }
private fun getResearches(buildingData: List<RgdData>, researches: Set<Research>): List<Research> {
return buildingData.getRgdTableByName("research_ext")
?.getRgdTableByName("research_table")
?.mapNotNull { research ->
if (research.name.contains("research_")) {
val fileName = research.value.toString().split("\\").last().replace(".lua", "")
researches.find { it.filename?.replace(".rgd", "") == fileName }
} else null
} ?: emptyList()
}
} }

View File

@ -0,0 +1,137 @@
package com.dowstats.service.w40k
import com.dowstats.Metadata.Requirements
import com.dowstats.data.dto.AreaEffect
import com.dowstats.data.dto.BuildCost
import com.dowstats.data.dto.ResourceIncome
import com.dowstats.data.dto.UiInfo
import com.dowstats.data.entities.Mod
import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.Logger
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class CommonParseRgdService @Autowired constructor(
private val modAttribPathService: ModAttribPathService,
private val iconsService: IconsService,
) {
fun getRequirementReference(ref: String?, rTable: List<RgdData>, fileName: String, log: Logger): String? = when (ref) {
Requirements.REFERENCE_REQUIREMENT_POP -> rTable.getDoubleByName("population_required").toString()
Requirements.REFERENCE_REQUIREMENT_ADDON -> rTable.getStringByName("addon_name")
Requirements.REFERENCE_GLOBAL_REQUIREMENT_ADDON -> rTable.getStringByName("global_addon_name")
Requirements.REFERENCE_REQUIREMENT_STRUCTURE_EITHER -> rTable.getStringByName("structure_name_or") + ";" + rTable.getStringByName(
"structure_name_either"
)
Requirements.REFERENCE_REQUIREMENT_STRUCTURE -> rTable.getStringByName("structure_name")
Requirements.REFERENCE_REQUIREMENT_RESEARCH -> rTable.getStringByName("research_name")+ ";" + rTable.getBooleanByName(
"research_must_not_be_complete"
)
else -> {
log.warn("Unknown requirement reference $ref at $fileName")
null
}
}
fun getBuildCost(cost: List<RgdData>?): BuildCost {
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()
)
}
fun getResourceIncome(resourceExt: List<RgdData>?): ResourceIncome {
return ResourceIncome(
resourceExt?.getDoubleByName("faith_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("power_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("requisition_per_second")?.let { it * 10 },
)
}
fun getUiInfo(uiInfoRgdData: List<RgdData>, modDictionary: Map<Int, String>, modFolderData: String, mod: Mod, log: Logger): UiInfo {
val nameRef = uiInfoRgdData?.getStringByName("screen_name_id")?.replace("$", "")
val name = nameRef?.let { try { modDictionary[it.toInt()] } catch (e: Exception) { null } }
val descriptionRefs = uiInfoRgdData.getRgdTableByName("help_text_list")
?.mapNotNull{try {
(it.value as String).replace("$", "")
} catch (e: Exception) {
log.error("Error parsing ui help_text weapon $name", e)
null
}}
?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
val description = try {
descriptionRefs?.map { try { modDictionary[it.toInt()] } catch (e: Exception) { "" } }?.joinToString ( "\n" )
} catch(e:Exception) {
log.warn("Error parsing ui description weapon $name", e)
null
}
val icon = try {
val iconPath = uiInfoRgdData.getStringByName("icon_name")
iconPath?.let { modAttribPathService.getIconPath(modFolderData, iconPath) }
?.let { iconsService.convertTgaToJpegImage(iconPath, it, mod.name) }
} catch (e: Exception) {
log.error("Error parsing ui icon path for weapon $name", e)
null
}
return UiInfo(name, description, icon)
}
fun getAreaEffectData(weaponData: List<RgdData>): AreaEffect {
val areaEffect = weaponData.getRgdTableByName("area_effect")
val areaEffectInformation = areaEffect?.getRgdTableByName("area_effect_information")
val cantHaveRadius = areaEffectInformation?.getRgdTableByName("area_type")?.getStringByName("\$REF")?.contains("tp_area_effect_point") == true
val damageRadius = if(cantHaveRadius) 0.0 else areaEffectInformation?.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") ?: 0.0
val maxDamage = armourDamage?.getDoubleByName("max_damage") ?: 0.0
val minDamageValue = armourDamage?.getDoubleByName("min_damage_value") ?: 0.0
val moraleDamage = armourDamage?.getDoubleByName("morale_damage") ?: 0.0
val defaultArmourPiercing = armourDamage?.getDoubleByName("armour_piercing") ?: 0.0
val weaponDmgMap: Map<String, Double> =
armourDamage?.getRgdTableByName("armour_piercing_types")?.mapNotNull { armour_piercing ->
if (armour_piercing.name.contains("entry")) {
val entry = armour_piercing.value as List<RgdData>
val dmgType = entry.getRgdTableByName("armour_type")?.getStringByName("\$REF")?.replace("type_armour\\tp_","")?.replace(".lua","")
val dmgValue = entry.getDoubleByName("armour_piercing_value")
dmgType!! to dmgValue!!
} else null
}?.toMap() ?: emptyMap()
return AreaEffect(minDamage, maxDamage, damageRadius, forceMin, forceMax, minDamageValue, moraleDamage, weaponDmgMap, defaultArmourPiercing)
}
}

View File

@ -43,6 +43,7 @@ class IconsService @Autowired constructor(
null null
} else { } else {
pathToSave.replace("${storageConfig.iconsStorage.replace("/", File.separator)}${File.separator}", "") pathToSave.replace("${storageConfig.iconsStorage.replace("/", File.separator)}${File.separator}", "")
.replace("${File.separator}${File.separator}","${File.separator}")
} }
} catch (e: Exception) { } catch (e: Exception) {
log.warn("Can't convert icon $iconPathInMod", e) log.warn("Can't convert icon $iconPathInMod", e)

View File

@ -26,6 +26,9 @@ class ModAttribPathService @Autowired constructor(
fun getWeaponAttribsPath(modFolderData: String): String = fun getWeaponAttribsPath(modFolderData: String): String =
"$modFolderData${File.separator}attrib${File.separator}weapon" "$modFolderData${File.separator}attrib${File.separator}weapon"
fun getResearchAttribsPath(modFolderData: String): String =
"$modFolderData${File.separator}attrib${File.separator}research"
fun getAddonAttribsPath(modFolderData: String): String = fun getAddonAttribsPath(modFolderData: String): String =
"$modFolderData${File.separator}attrib${File.separator}addons" "$modFolderData${File.separator}attrib${File.separator}addons"

View File

@ -1,8 +1,10 @@
package com.dowstats.service.w40k package com.dowstats.service.w40k
import com.dowstats.data.entities.* import com.dowstats.data.entities.*
import com.dowstats.data.entities.research.Research
import com.dowstats.data.repositories.* import com.dowstats.data.repositories.*
import com.dowstats.data.rgd.RgdData import com.dowstats.data.rgd.RgdData
import com.dowstats.service.postparsing.PostParsingService
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.lang.Exception import java.lang.Exception
@ -17,6 +19,7 @@ import kotlin.io.path.name
class ModParserService @Autowired constructor( class ModParserService @Autowired constructor(
val rgdParserService: RgdParserService, val rgdParserService: RgdParserService,
val unitRgdExtractService: UnitRgdExtractService, val unitRgdExtractService: UnitRgdExtractService,
val researchRgdExtractService: ResearchRgdExtractService,
val raceRepository: RaceRepository, val raceRepository: RaceRepository,
val armorTypeRepository: ArmorTypeRepository, val armorTypeRepository: ArmorTypeRepository,
val unitRepository: UnitRepository, val unitRepository: UnitRepository,
@ -26,6 +29,9 @@ class ModParserService @Autowired constructor(
val modAttribPathService: ModAttribPathService, val modAttribPathService: ModAttribPathService,
val buildingRepository: BuildingRepository, val buildingRepository: BuildingRepository,
val buildingExtractService: BuildingRgdExtractService, val buildingExtractService: BuildingRgdExtractService,
val postParsingService: PostParsingService,
private val researchRepository: ResearchRepository,
private val modRepository: ModRepository,
) { ) {
val defaultDictionary: MutableMap<Int, String> = mutableMapOf() val defaultDictionary: MutableMap<Int, String> = mutableMapOf()
@ -56,6 +62,7 @@ class ModParserService @Autowired constructor(
fun parseModFilesAndSaveToDb(mod: Mod) { fun parseModFilesAndSaveToDb(mod: Mod) {
log.info("Start parse mod files ${mod.technicalName}:${mod.version}") log.info("Start parse mod files ${mod.technicalName}:${mod.version}")
modRepository.clearModData(mod.id!!)
val modFolderData = modAttribPathService.getModFolderData(mod.technicalName!!, mod.version!!) val modFolderData = modAttribPathService.getModFolderData(mod.technicalName!!, mod.version!!)
val racesList = Files.walk(Path(modAttribPathService.getSbpsAttribsFolderPath(modFolderData)), 1) val racesList = Files.walk(Path(modAttribPathService.getSbpsAttribsFolderPath(modFolderData)), 1)
@ -78,9 +85,14 @@ class ModParserService @Autowired constructor(
val enrichedModDictionary = defaultDictionary + modDictionary val enrichedModDictionary = defaultDictionary + modDictionary
val weapons = saveWeapons(modFolderData, mod, armorTypes, enrichedModDictionary) val weapons = saveWeapons(modFolderData, mod, armorTypes, enrichedModDictionary)
val researches = saveResearches(modFolderData, mod, enrichedModDictionary)
saveUnits(modFolderData, weapons, racesList, armorTypes, 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}")
saveBuildings(modFolderData, weapons, racesList, armorTypes, mod, enrichedModDictionary)
} }
@ -110,6 +122,49 @@ class ModParserService @Autowired constructor(
return modDictionary return modDictionary
} }
private fun saveResearches(
modFolderData: String,
mod: Mod,
modDictionary: Map<Int, String>
): Set<Research> {
val classicRgdDataResearches =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getResearchAttribsPath(
modAttribPathService.pathToWanilaData,
)
)
val modRgdDataResearches =
rgdParserService.parseFolderToRgdFiles(
modAttribPathService.getResearchAttribsPath(
modFolderData,
)
)
val researchesFull = classicRgdDataResearches + modRgdDataResearches
val researches = researchesFull.mapNotNull { researchData ->
try {
researchRgdExtractService.extractToResearchEntity(
researchData.key,
modDictionary,
researchData.value,
modFolderData,
mod,
)
} catch (e: Exception) {
log.error("Can't extract ${researchData.key}", e)
null
}
}
return try {
researchRepository.saveAll(researches).toSet()
} catch (e: Exception) {
throw e
}
}
private fun saveUnits( private fun saveUnits(
modFolderData: String, modFolderData: String,
weapons: Set<Weapon>, weapons: Set<Weapon>,
@ -173,6 +228,7 @@ class ModParserService @Autowired constructor(
try { try {
unitRgdExtractService.extractToUnitEntity( unitRgdExtractService.extractToUnitEntity(
squadRgdData.key, squadRgdData.key,
baseUnitName,
modDictionary, modDictionary,
squadRgdData.value, squadRgdData.value,
unitRgdData, unitRgdData,
@ -230,6 +286,7 @@ class ModParserService @Autowired constructor(
sergeantRepository.save(sergeant) sergeantRepository.save(sergeant)
} }
} catch (e: Exception) { } catch (e: Exception) {
log.error("Cant save unit ${unitDataToSave.unit.name}", e)
throw e throw e
} }
} }
@ -239,6 +296,7 @@ class ModParserService @Autowired constructor(
private fun saveBuildings( private fun saveBuildings(
modFolderData: String, modFolderData: String,
weapons: Set<Weapon>, weapons: Set<Weapon>,
researches: Set<Research>,
racesList: List<String>, racesList: List<String>,
armorTypes: Set<ArmorType>, armorTypes: Set<ArmorType>,
mod: Mod, modDictionary: Map<Int, String> mod: Mod, modDictionary: Map<Int, String>
@ -282,6 +340,7 @@ class ModParserService @Autowired constructor(
modDictionary, modDictionary,
structure.value, structure.value,
weapons, weapons,
researches,
raceUnits, raceUnits,
race, race,
modFolderData, modFolderData,
@ -356,4 +415,6 @@ class ModParserService @Autowired constructor(
} }
} }
} }

View File

@ -0,0 +1,88 @@
package com.dowstats.service.w40k
import com.dowstats.data.entities.*
import com.dowstats.data.entities.research.Research
import com.dowstats.data.entities.research.ResearchModifiers
import com.dowstats.data.entities.research.ResearchRequirements
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
@Service
class ResearchRgdExtractService @Autowired constructor(
private val commonParseRgdService: CommonParseRgdService,
) {
val log = LoggerFactory.getLogger(ResearchRgdExtractService::class.java)
fun extractToResearchEntity(
fileName: String,
modDictionary: Map<Int, String>,
researchData: List<RgdData>,
modFolderData: String,
mod: Mod,
): Research {
val research = Research()
research.filename = fileName
val uiInfo = researchData.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
val researchUiInfo = commonParseRgdService.getUiInfo(uiInfo, modDictionary, modFolderData, mod, log)
research.name = researchUiInfo.name
research.description = researchUiInfo.description
research.icon = researchUiInfo.iconPath
val cost = commonParseRgdService.getBuildCost(researchData.getRgdTableByName("time_cost"))
research.costRequisition = cost.requisition
research.costPower = cost.power
research.costFaith = cost.faith
research.costSouls = cost.souls
research.costTime = cost.time
research.researchModifiers = getResearchModifiers(research, researchData).toMutableSet()
research.addonRequirements = getResearchRequirements(research, researchData).toMutableSet()
research.modId = mod.id
return research
}
private fun getResearchModifiers(research: Research, researchData: List<RgdData>): List<ResearchModifiers> {
val modifiers = researchData.getRgdTableByName("modifiers")
return modifiers?.mapNotNull { m ->
if (m.name.contains("modifier_")) {
val mTable = m.value as List<RgdData>
if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua") null else {
ResearchModifiers().also {
it.research = research
it.reference = mTable.getStringByName("\$REF")
it.usageType = mTable.getRgdTableByName("usage_type")?.getStringByName("\$REF")
it.target = mTable.getStringByName("target_type_name")
it.value = mTable.getDoubleByName("value")
}
}
} else null
} ?: emptyList()
}
private fun getResearchRequirements(research: Research, researchData: List<RgdData>): List<ResearchRequirements> {
val requirements = researchData.getRgdTableByName("requirements")
return requirements?.mapNotNull { r ->
if (r.name.contains("required_")) {
val rTable = r.value as List<RgdData>
if (rTable.getStringByName("\$REF") == "requirements\\required_none.lua") null else {
ResearchRequirements().also {
it.research = research
it.reference = rTable.getStringByName("\$REF")
it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, research.filename!!, log)
}
}
} else null
} ?: emptyList()
}
}

View File

@ -13,7 +13,7 @@ import java.nio.ByteBuffer
class RgdParserService @Autowired constructor( class RgdParserService @Autowired constructor(
) { ) {
val log = LoggerFactory.getLogger(WeaponRgdExtractService::class.java) val log = LoggerFactory.getLogger(RgdParserService::class.java)
val zeroByte: Byte = 0 val zeroByte: Byte = 0

View File

@ -7,18 +7,13 @@ import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getIntByName import com.dowstats.data.rgd.RgdDataUtil.getIntByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import com.dowstats.service.w40k.UnitRgdExtractService.ResourceIncome
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.Path
import kotlin.io.path.exists
@Service @Service
class SergeantRgdExtractService @Autowired constructor( class SergeantRgdExtractService @Autowired constructor(
private val modAttribPathService: ModAttribPathService, private val commonParseRgdService: CommonParseRgdService,
private val iconsService: IconsService,
) { ) {
val log = LoggerFactory.getLogger(SergeantRgdExtractService::class.java) val log = LoggerFactory.getLogger(SergeantRgdExtractService::class.java)
@ -42,11 +37,6 @@ class SergeantRgdExtractService @Autowired constructor(
val upTime: Double?, val upTime: Double?,
) )
data class SquadTexts(
val name: String?,
val description: String?,
)
fun extractToSergeantEntity( fun extractToSergeantEntity(
fileName: String, fileName: String,
modDictionary: Map<Int, String>, modDictionary: Map<Int, String>,
@ -69,19 +59,23 @@ class SergeantRgdExtractService @Autowired constructor(
sergeant.armorType = getUnitArmorType(sergeantData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type") sergeant.armorType = getUnitArmorType(sergeantData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
sergeant.armorType2 = getUnitArmorType(sergeantData, armorTypes, "type_armour_2") sergeant.armorType2 = getUnitArmorType(sergeantData, armorTypes, "type_armour_2")
val nameAndDescription = getSergeantNameAndDescription(sergeantData, modDictionary)
sergeant.name = nameAndDescription.name
sergeant.description = nameAndDescription.description
sergeant.filename = fileName sergeant.filename = fileName
val sergeantUiRgd = sergeantData.getRgdTableByName("ui_ext")
?.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
val sergeantUiInfo = commonParseRgdService.getUiInfo(sergeantUiRgd, modDictionary, modFolderData, mod, log)
sergeant.name = sergeantUiInfo.name
sergeant.description = sergeantUiInfo.description
sergeant.icon = sergeantUiInfo.iconPath
val healthData = getHealthAndMoraleDeathPenaltyData(sergeantData) val healthData = getHealthAndMoraleDeathPenaltyData(sergeantData)
sergeant.health = healthData.hitpoints?.toInt() sergeant.health = healthData.hitpoints?.toInt()
sergeant.armour = healthData.armour sergeant.armour = healthData.armour
sergeant.healthRegeneration = healthData.regeneration sergeant.healthRegeneration = healthData.regeneration
sergeant.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt() sergeant.moraleDeathPenalty = healthData.moraleDeathPenalty?.toInt()
val incomeData = getResource(sergeantData) val incomeData = commonParseRgdService.getResourceIncome(sergeantData.getRgdTableByName("resource_ext"))
sergeant.faithIncome = incomeData.faith sergeant.faithIncome = incomeData.faith
sergeant.powerIncome = incomeData.power sergeant.powerIncome = incomeData.power
sergeant.requisitionIncome = incomeData.requisition sergeant.requisitionIncome = incomeData.requisition
@ -93,8 +87,7 @@ class SergeantRgdExtractService @Autowired constructor(
sergeant.mass = massData.mass sergeant.mass = massData.mass
sergeant.upTime = massData.upTime sergeant.upTime = massData.upTime
val unitIcon = convertSergeantIconAndReturnPath(sergeantData, modFolderData, mod.name) sergeant.modId = mod.id!!
sergeant.icon = unitIcon
val sergeantWeapons = getSergeantWeapons(sergeantData)?.mapNotNull { weaponData -> val sergeantWeapons = getSergeantWeapons(sergeantData)?.mapNotNull { weaponData ->
weapons.find { weapons.find {
@ -119,29 +112,6 @@ class SergeantRgdExtractService @Autowired constructor(
return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") } return armorTypes.find { it.id == armorType?.replace("type_armour\\tp_", "")?.replace(".lua", "") }
} }
private fun getSergeantNameAndDescription(sergeantData: List<RgdData>, modDictionary: Map<Int, String>): SquadTexts {
val uiInfo = sergeantData.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 SquadTexts(name, description)
}
private fun getHealthAndMoraleDeathPenaltyData(unitData: List<RgdData>): HealthAndMoraleDeathData { private fun getHealthAndMoraleDeathPenaltyData(unitData: List<RgdData>): HealthAndMoraleDeathData {
val healthExt = unitData.getRgdTableByName("health_ext") val healthExt = unitData.getRgdTableByName("health_ext")
@ -153,14 +123,6 @@ class SergeantRgdExtractService @Autowired constructor(
) )
} }
private fun getResource(sergeantData: List<RgdData>): ResourceIncome {
val resourceExt = sergeantData.getRgdTableByName("resource_ext")
return ResourceIncome(
resourceExt?.getDoubleByName("faith_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("power_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("requisition_per_second")?.let { it * 10 },
)
}
private fun getMassData(unitData: List<RgdData>): MassData { private fun getMassData(unitData: List<RgdData>): MassData {
val massDataRgd = unitData val massDataRgd = unitData
@ -201,14 +163,4 @@ class SergeantRgdExtractService @Autowired constructor(
} else null } else null
}?.flatten() }?.flatten()
private fun convertSergeantIconAndReturnPath(sergeantData: List<RgdData>, modFolderData: String, modName: String?): String? {
val iconPathInMod = sergeantData
.getRgdTableByName("ui_ext")
?.getRgdTableByName("ui_info")
?.getStringByName("icon_name")
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
} }

View File

@ -16,8 +16,7 @@ import org.springframework.stereotype.Service
@Service @Service
class UnitRgdExtractService @Autowired constructor( class UnitRgdExtractService @Autowired constructor(
private val modAttribPathService: ModAttribPathService, private val commonParseRgdService: CommonParseRgdService,
private val iconsService: IconsService,
private val sergeantRgdExtractService: SergeantRgdExtractService, private val sergeantRgdExtractService: SergeantRgdExtractService,
) { ) {
@ -36,12 +35,6 @@ class UnitRgdExtractService @Autowired constructor(
val percentCost: Int?, val percentCost: Int?,
) )
data class ResourceIncome(
val faith: Double?,
val power: Double?,
val requisition: Double?,
)
data class MoraleData( data class MoraleData(
val max: Double?, val max: Double?,
val broken: Double?, val broken: Double?,
@ -58,13 +51,10 @@ class UnitRgdExtractService @Autowired constructor(
val upTime: Double?, val upTime: Double?,
) )
data class UnitTexts(
val name: String?,
val description: String?,
)
fun extractToUnitEntity( fun extractToUnitEntity(
fileName: String, fileNameSquad: String,
fileNameUnit: String,
modDictionary: Map<Int, String>, modDictionary: Map<Int, String>,
squadData: List<RgdData>, squadData: List<RgdData>,
unitData: List<RgdData>, unitData: List<RgdData>,
@ -84,10 +74,14 @@ class UnitRgdExtractService @Autowired constructor(
unit.armorType = getUnitArmorType(unitData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type") unit.armorType = getUnitArmorType(unitData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
unit.armorType2 = getUnitArmorType(unitData, armorTypes, "type_armour_2") unit.armorType2 = getUnitArmorType(unitData, armorTypes, "type_armour_2")
val nameAndDescription = getUnitNameAndDescription(squadData, modDictionary) val squadUiInfo = squadData.getRgdTableByName("squad_ui_ext")
unit.name = nameAndDescription.name ?.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileNameSquad")
unit.description = nameAndDescription.description val squadUiData = commonParseRgdService.getUiInfo(squadUiInfo, modDictionary, modFolderData, mod, log)
unit.filename = fileName unit.name = squadUiData.name
unit.description = squadUiData.description
unit.icon = squadUiData.iconPath
unit.filenameSquad = fileNameSquad
unit.filenameUnit = fileNameUnit
val buildCost = getBuildCost(unitData, squadData) val buildCost = getBuildCost(unitData, squadData)
unit.buildCostRequisition = buildCost.requisition unit.buildCostRequisition = buildCost.requisition
@ -117,7 +111,7 @@ class UnitRgdExtractService @Autowired constructor(
unit.repairSpeed = repairData.healthPerSecond unit.repairSpeed = repairData.healthPerSecond
unit.repairCostPercent = repairData.percentCost unit.repairCostPercent = repairData.percentCost
val incomeData = getResource(unitData) val incomeData = commonParseRgdService.getResourceIncome(unitData.getRgdTableByName("resource_ext"))
unit.faithIncome = incomeData.faith unit.faithIncome = incomeData.faith
unit.powerIncome = incomeData.power unit.powerIncome = incomeData.power
unit.requisitionIncome = incomeData.requisition unit.requisitionIncome = incomeData.requisition
@ -135,14 +129,14 @@ class UnitRgdExtractService @Autowired constructor(
unit.mass = massData.mass unit.mass = massData.mass
unit.upTime = massData.upTime unit.upTime = massData.upTime
val reinforceData = getReinforceRgdData(squadData) val reinforceCostRgdData = getReinforceCostRgdData(squadData)
val reinforceCostData = reinforceData?.getRgdTableByName("cost") val reinforceCostData = commonParseRgdService.getBuildCost(reinforceCostRgdData)
unit.reinforceCostRequisition = getReinforceRequisition(reinforceCostData) unit.reinforceCostRequisition = reinforceCostData.requisition
unit.reinforceCostPower = getReinforcePower(reinforceCostData) unit.reinforceCostPower = reinforceCostData.power
unit.reinforceCostPopulation = getReinforcePopulation(reinforceCostData) unit.reinforceCostPopulation = reinforceCostData.population
unit.reinforceCostFaith = getReinforceFaith(reinforceCostData) unit.reinforceCostFaith = reinforceCostData.faith
unit.reinforceCostSouls = getReinforceSouls(reinforceCostData) unit.reinforceCostSouls = reinforceCostData.souls
unit.reinforceTime = getReinforceTime(reinforceData) unit.reinforceTime = reinforceCostData.time
@ -167,8 +161,6 @@ class UnitRgdExtractService @Autowired constructor(
unit.maxSergeants = sergeantsData.second unit.maxSergeants = sergeantsData.second
val unitIcon = convertIconAndReturnPath(mod.name, squadData, modFolderData)
unit.icon = unitIcon
val unitWeapons = getUnitWeapons(unitData, weapons) val unitWeapons = getUnitWeapons(unitData, weapons)
@ -201,11 +193,7 @@ class UnitRgdExtractService @Autowired constructor(
private fun getBuildCost(unitData: List<RgdData>, squadData: List<RgdData>): BuildCost { private fun getBuildCost(unitData: List<RgdData>, squadData: List<RgdData>): BuildCost {
val cost = unitData.getRgdTableByName("cost_ext") val cost = commonParseRgdService.getBuildCost(unitData.getRgdTableByName("cost_ext")?.getRgdTableByName("time_cost"))
?.getRgdTableByName("time_cost")
val costResources = cost
?.getRgdTableByName("cost")
val minSquadSize = squadData.getRgdTableByName("squad_loadout_ext") val minSquadSize = squadData.getRgdTableByName("squad_loadout_ext")
?.getDoubleByName("unit_min")?.toInt() ?.getDoubleByName("unit_min")?.toInt()
@ -214,45 +202,15 @@ class UnitRgdExtractService @Autowired constructor(
cost?.let { it * (minSquadSize ?: 1) } cost?.let { it * (minSquadSize ?: 1) }
return BuildCost( return BuildCost(
getCost(costResources?.getDoubleByName("requisition")), getCost(cost.requisition),
getCost(costResources?.getDoubleByName("power")), getCost(cost.power),
getCost(costResources?.getDoubleByName("population")), getCost(cost.population),
getCost(costResources?.getDoubleByName("faith")), getCost(cost.faith),
getCost(costResources?.getDoubleByName("souls")), getCost(cost.souls),
getCost(cost?.getDoubleByName("time_seconds"))?.toInt() getCost(cost.time?.toDouble())?.toInt()
) )
} }
private fun getUnitNameAndDescription(squadData: List<RgdData>, modDictionary: Map<Int, String>): UnitTexts {
val uiInfo = squadData.getRgdTableByName("squad_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 != "" && it.toIntOrNull() != null }
?.sortedBy { it.toInt() }
val description = try {
descriptionRefs?.map {
modDictionary[it.toInt()] }?.joinToString("\n")
} catch (e: Exception) {
log.warn("Error parsing ui description", e)
null
}
return UnitTexts(name, description)
}
private fun getSquadCap(squadData: List<RgdData>): Pair<Double?, Double?> { private fun getSquadCap(squadData: List<RgdData>): Pair<Double?, Double?> {
@ -307,14 +265,6 @@ class UnitRgdExtractService @Autowired constructor(
) )
} }
private fun getResource(unitData: List<RgdData>): ResourceIncome {
val resourceExt = unitData.getRgdTableByName("resource_ext")
return ResourceIncome(
resourceExt?.getDoubleByName("faith_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("power_per_second")?.let { it * 10 },
resourceExt?.getDoubleByName("requisition_per_second")?.let { it * 10 },
)
}
private fun getMoraleData(squadData: List<RgdData>): MoraleData { private fun getMoraleData(squadData: List<RgdData>): MoraleData {
val moraleData = squadData.getRgdTableByName("squad_morale_ext") val moraleData = squadData.getRgdTableByName("squad_morale_ext")
@ -346,28 +296,10 @@ class UnitRgdExtractService @Autowired constructor(
.getRgdTableByName("sight_ext") .getRgdTableByName("sight_ext")
?.getDoubleByName("keen_sight_radius") ?.getDoubleByName("keen_sight_radius")
private fun getReinforceRgdData(squadData: List<RgdData>): List<RgdData>? = squadData private fun getReinforceCostRgdData(squadData: List<RgdData>): List<RgdData>? = squadData
.getRgdTableByName("squad_reinforce_ext") .getRgdTableByName("squad_reinforce_ext")
?.getRgdTableByName("cost") ?.getRgdTableByName("cost")
private fun getReinforceRequisition(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("requisition")
private fun getReinforcePower(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("power")
private fun getReinforcePopulation(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("population")
private fun getReinforceFaith(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("faith")
private fun getReinforceSouls(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("souls")
private fun getReinforceTime(reinforceData: List<RgdData>?): Int? = reinforceData
?.getIntByName("time_seconds")
private fun getUnitWeapons(reinforceData: List<RgdData>?, weapons: Set<Weapon>): List<WeaponsData>? = reinforceData private fun getUnitWeapons(reinforceData: List<RgdData>?, weapons: Set<Weapon>): List<WeaponsData>? = reinforceData
?.getRgdTableByName("combat_ext") ?.getRgdTableByName("combat_ext")
?.getRgdTableByName("hardpoints") ?.getRgdTableByName("hardpoints")
@ -382,9 +314,9 @@ class UnitRgdExtractService @Autowired constructor(
it.mapNotNull { weapon -> it.mapNotNull { weapon ->
(weapon.value as? List<RgdData>)?.getStringByName("weapon")?.let { (weapon.value as? List<RgdData>)?.getStringByName("weapon")?.let {
if (it != "") { if (it != "") {
val weaponFileName = it.replace("weapon\\", "").replace(".lua", ".rgd") val weaponFileName = it.replace("weapon\\", "").replace(".lua", "")
val weaponEntity = weapons.find { val weaponEntity = weapons.find {
it.filename == weaponFileName it.filename?.replace(".rgd", "") == weaponFileName
} }
if(weaponEntity == null){ if(weaponEntity == null){
log.warn("Can't find weapon $weaponFileName") log.warn("Can't find weapon $weaponFileName")
@ -418,19 +350,9 @@ class UnitRgdExtractService @Autowired constructor(
if (sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else { if (sergeantLeaderFilePath == null || sergeantLeaderFilePath == "") null else {
val cost = sergeantRgdTable.getRgdTableByName("cost_time")
val costResources = cost?.getRgdTableByName("cost")
SergeantData( SergeantData(
sergeantLeaderFilePath, sergeantLeaderFilePath,
BuildCost( commonParseRgdService.getBuildCost(sergeantRgdTable.getRgdTableByName("cost_time"))
costResources?.getDoubleByName("requisition"),
costResources?.getDoubleByName("power"),
costResources?.getDoubleByName("population"),
costResources?.getDoubleByName("faith"),
costResources?.getDoubleByName("souls"),
cost?.getIntByName("time_seconds"),
)
) )
} }
} else null } else null
@ -438,15 +360,4 @@ class UnitRgdExtractService @Autowired constructor(
return Pair(sergeantsData, maxSergeants) return Pair(sergeantsData, maxSergeants)
} }
private fun convertIconAndReturnPath(modName: String?, squadData: List<RgdData>, modFolderData: String): String? {
val iconPathInMod = squadData
.getRgdTableByName("squad_ui_ext")
?.getRgdTableByName("ui_info")
?.getStringByName("icon_name")
return iconPathInMod?.let { modAttribPathService.getIconPath(modFolderData, iconPathInMod) }
?.let { iconsService.convertTgaToJpegImage(iconPathInMod, it, modName) }
}
} }

View File

@ -4,69 +4,40 @@ import com.dowstats.data.entities.ArmorType
import com.dowstats.data.entities.Mod import com.dowstats.data.entities.Mod
import com.dowstats.data.entities.Weapon import com.dowstats.data.entities.Weapon
import com.dowstats.data.entities.WeaponArmorPiercing import com.dowstats.data.entities.WeaponArmorPiercing
import com.dowstats.data.repositories.ArmorTypeRepository
import com.dowstats.data.rgd.RgdData import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName 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.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.File
import java.math.BigDecimal import java.math.BigDecimal
import java.nio.file.Path
import kotlin.io.path.exists
@Service @Service
class WeaponRgdExtractService @Autowired constructor( class WeaponRgdExtractService @Autowired constructor(
private val armorTypeRepository: ArmorTypeRepository, private val commonParseRgdService: CommonParseRgdService,
private val iconsService: IconsService,
private val modAttribPathService: ModAttribPathService,
) { ) {
val log = LoggerFactory.getLogger(WeaponRgdExtractService::class.java) val log = LoggerFactory.getLogger(WeaponRgdExtractService::class.java)
data class BuildCost(
val requisition: Int?,
val power: Int?,
val seconds: Int?
)
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<WeaponArmorPiercing>,
)
data class WeaponUiInfo(
val name: String?,
val description: String?,
val iconPath: String?,
val haveEquipButton: Boolean
)
fun extractToWeaponEntity(weaponFileName: String, weaponData: List<RgdData>, mod: Mod, armorTypes: Set<ArmorType>, modFolderData: String, modDictionary: Map<Int, String>): Weapon? { fun extractToWeaponEntity(weaponFileName: String, weaponData: List<RgdData>, mod: Mod, armorTypes: Set<ArmorType>, modFolderData: String, modDictionary: Map<Int, String>): Weapon? {
val weapon = Weapon() val weapon = Weapon()
weapon.filename = weaponFileName weapon.filename = weaponFileName
val weaponUiData = getWeaponNameAndDescription(weaponData, modDictionary, modFolderData, mod)
val weaponUiInfo = weaponData.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $weaponFileName")
val weaponUiData = commonParseRgdService.getUiInfo(weaponUiInfo, modDictionary, modFolderData, mod, log)
weapon.name = weaponUiData.name weapon.name = weaponUiData.name
weapon.icon = weaponUiData.iconPath weapon.icon = weaponUiData.iconPath
weapon.description = weaponUiData.description weapon.description = weaponUiData.description
weapon.haveEquipButton = weaponUiData.haveEquipButton weapon.haveEquipButton = weaponUiInfo.getBooleanByName("no_button")?.let { !it } ?: false
val cost = getCost(weaponData) val cost = commonParseRgdService.getBuildCost(weaponData.getRgdTableByName("cost"))
weapon.costRequisition = cost.requisition ?: 0 weapon.costRequisition = cost.requisition ?: 0.0
weapon.costPower = cost.power ?: 0 weapon.costPower = cost.power ?: 0.0
weapon.costTimeSeconds = cost.seconds ?: 0 weapon.costTimeSeconds = cost.time ?: 0
weapon.accuracy = weaponData.getDoubleByName("accuracy") ?: 1.0 weapon.accuracy = weaponData.getDoubleByName("accuracy") ?: 1.0
weapon.accuracyReductionMoving = weaponData.getDoubleByName("accuracy_reduction_when_moving") weapon.accuracyReductionMoving = weaponData.getDoubleByName("accuracy_reduction_when_moving")
weapon.minRange = weaponData.getDoubleByName("min_range") weapon.minRange = weaponData.getDoubleByName("min_range")
@ -79,14 +50,24 @@ class WeaponRgdExtractService @Autowired constructor(
weapon.canAttackGround = weaponData.getBooleanByName("can_attack_ground_units") ?: true weapon.canAttackGround = weaponData.getBooleanByName("can_attack_ground_units") ?: true
val areaEffectData = getAreaEffectData(weaponData, armorTypes, weapon) val areaEffectData = commonParseRgdService.getAreaEffectData(weaponData)
val armoursPiercing = armorTypes.map {
val weaponArmourPiercing = WeaponArmorPiercing()
weaponArmourPiercing.weapon = weapon
weaponArmourPiercing.armorType = it
val piercingValue = (areaEffectData.weaponDmgMap[it.id] ?: areaEffectData.defaultArmorPiercing).toBigDecimal()
weaponArmourPiercing.piercingValue = piercingValue.let {pv -> if(pv <= BigDecimal(100)) pv else BigDecimal(100) }
weaponArmourPiercing
}
weapon.minDamageValue = areaEffectData.minDamageValue weapon.minDamageValue = areaEffectData.minDamageValue
weapon.minDamage = areaEffectData.minDamage weapon.minDamage = areaEffectData.minDamage
weapon.maxDamage = areaEffectData.maxDamage weapon.maxDamage = areaEffectData.maxDamage
weapon.throwForceMin = areaEffectData.throwForceMin weapon.throwForceMin = areaEffectData.throwForceMin
weapon.throwForceMax = areaEffectData.throwForceMax weapon.throwForceMax = areaEffectData.throwForceMax
weapon.moraleDamage = areaEffectData.moraleDamage weapon.moraleDamage = areaEffectData.moraleDamage
weapon.weaponPiercings = areaEffectData.armourPiercing weapon.weaponPiercings = armoursPiercing
weapon.damageRadius = areaEffectData.damageRadius weapon.damageRadius = areaEffectData.damageRadius
weapon.modId = mod.id weapon.modId = mod.id
@ -95,94 +76,5 @@ class WeaponRgdExtractService @Autowired constructor(
} else weapon } else weapon
} }
private fun getCost(weaponData: List<RgdData>): BuildCost {
val costTable = weaponData.getRgdTableByName("cost")
val costTime = costTable?.getIntByName("time_seconds")
val costCost = costTable?.getRgdTableByName("cost")
val power = costCost?.getIntByName("power")
val requisition = costCost?.getIntByName("requisition")
return BuildCost(requisition, power, costTime)
}
private fun getAreaEffectData(weaponData: List<RgdData>, armorTypes: Set<ArmorType>, thisWeapon: Weapon): AreaEffectData {
val areaEffect = weaponData.getRgdTableByName("area_effect")
val areaEffectInformation = areaEffect?.getRgdTableByName("area_effect_information")
val cantHaveRadius = areaEffectInformation?.getRgdTableByName("area_type")?.getStringByName("\$REF")?.contains("tp_area_effect_point") == true
val damageRadius = if(cantHaveRadius) 0.0 else areaEffectInformation?.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") ?: 0.0
val maxDamage = armourDamage?.getDoubleByName("max_damage") ?: 0.0
val minDamageValue = armourDamage?.getDoubleByName("min_damage_value") ?: 0.0
val moraleDamage = armourDamage?.getDoubleByName("morale_damage") ?: 0.0
val defaultArmourPiercing = armourDamage?.getDoubleByName("armour_piercing")
val weaponDmgMap: Map<String, Double> =
armourDamage?.getRgdTableByName("armour_piercing_types")?.mapNotNull { armour_piercing ->
if (armour_piercing.name.contains("entry")) {
val entry = armour_piercing.value as List<RgdData>
val dmgType = entry.getRgdTableByName("armour_type")?.getStringByName("\$REF")?.replace("type_armour\\tp_","")?.replace(".lua","")
val dmgValue = entry.getDoubleByName("armour_piercing_value")
dmgType!! to dmgValue!!
} else null
}?.toMap() ?: emptyMap()
val armoursPiercing = armorTypes.map {
val weaponArmourPiercing = WeaponArmorPiercing()
weaponArmourPiercing.weapon = thisWeapon
weaponArmourPiercing.armorType = it
val piercingValue = (weaponDmgMap[it.id] ?: defaultArmourPiercing)?.toBigDecimal()
weaponArmourPiercing.piercingValue = piercingValue?.let {pv -> if(pv <= BigDecimal(100)) pv else BigDecimal(100) }
weaponArmourPiercing
}
return AreaEffectData(minDamage, maxDamage, damageRadius, forceMin, forceMax, minDamageValue, moraleDamage, armoursPiercing)
}
private fun getWeaponNameAndDescription(weaponData: List<RgdData>, modDictionary: Map<Int, String>, modFolderData: String, mod: Mod): WeaponUiInfo {
val weaponUiInfo = weaponData.getRgdTableByName("ui_info")
val nameRef = weaponUiInfo?.getStringByName("screen_name_id")?.replace("$", "")
val name = nameRef?.let { try { modDictionary[it.toInt()] } catch (e: Exception) { null } }
val descriptionRefs = weaponUiInfo?.getRgdTableByName("help_text_list")
?.mapNotNull{try {
(it.value as String).replace("$", "")
} catch (e: Exception) {
log.error("Error parsing ui help_text weapon $name", e)
null
}}
?.filter{it != "0" && it != "tables\\text_table.lua" && it != ""}
?.sortedBy { try { it.toInt() } catch (e: Exception) { 0 } }
val description = try {
descriptionRefs?.map { try { modDictionary[it.toInt()] } catch (e: Exception) { "" } }?.joinToString ( "\n" )
} catch(e:Exception) {
log.warn("Error parsing ui description weapon $name", e)
null
}
val icon = try {
val iconPath = weaponUiInfo?.getStringByName("icon_name")
iconPath?.let { modAttribPathService.getIconPath(modFolderData, iconPath) }
?.let { iconsService.convertTgaToJpegImage(iconPath, it, mod.name) }
} catch (e: Exception) {
log.error("Error parsing ui icon path for weapon $name", e)
null
}
val haveUpgradeButton = weaponUiInfo?.getBooleanByName("no_button")?.let { !it } ?: false
return WeaponUiInfo(name, description, icon, haveUpgradeButton)
}
} }

View File

@ -0,0 +1,65 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Update armor type names",
"author": "anibus",
"changes": [
{
"update": {
"columns": [
{
"column": {
"name": "name",
"value": "Infantry H.Med."
}
}
],
"tableName": "armor_types",
"where": "name='Infantry Heavy Medium'"
}
},{
"update": {
"columns": [
{
"column": {
"name": "name",
"value": "Infantry H.High"
}
}
],
"tableName": "armor_types",
"where": "name='Infantry Heavy High'"
}
},{
"update": {
"columns": [
{
"column": {
"name": "name",
"value": "Infantry Com."
}
}
],
"tableName": "armor_types",
"where": "name='Commander'"
}
},{
"update": {
"columns": [
{
"column": {
"name": "name",
"value": "Vehicle Air"
}
}
],
"tableName": "armor_types",
"where": "name='Air'"
}
}
]
}
}
]
}

View File

@ -0,0 +1,11 @@
CREATE OR REPLACE PROCEDURE delete_campaign_entities(mod_id_p bigint)
LANGUAGE SQL
AS $$
DELETE FROM units WHERE mod_id = mod_id_p AND (fileName_squad LIKE '%\_dxp3.%' OR fileName_squad LIKE '%\_dxp3.%' OR fileName_squad LIKE '%sp\_eldar\_%' OR fileName_squad LIKE '%\_sp\_%' OR fileName_squad LIKE '%\_sp.%' OR fileName_squad LIKE '%\_nis.%' OR fileName_squad LIKE '%\_interface\_relay.%' OR fileName_squad LIKE '%necron\_tunnel.rgd%' OR fileName_squad LIKE '%\_exarch\_council.%' OR fileName_squad LIKE '%\_dark\_reapers\_base.%' OR fileName_squad LIKE '%eldar\_deep\_strike\_building.rgd%' OR fileName_squad LIKE '%ork\_deep\_strike\_building.rgd%' OR fileName_squad LIKE '%\_caravel\_ai.rgd%' OR fileName_squad LIKE '%sisters\_tanktrap\_ai.rgd%' OR fileName_squad LIKE '%sisters\_hq\_ktgm.rgd%' OR fileName_squad LIKE '%tau\_squad\_slave\_murdered%' OR fileName_squad LIKE '%single\_player\_only%' OR fileName_squad LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR fileName_squad LIKE '%advance\_sp%' OR fileName_squad LIKE '%\_no\_limit.rgd%');
DELETE FROM buildings WHERE mod_id = mod_id_p AND (filename LIKE '%\_dxp3.%' OR filename LIKE '%\_dxp3.%' OR filename LIKE '%sp\_eldar\_%' OR filename LIKE '%\_sp\_%' OR filename LIKE '%\_sp.%' OR filename LIKE '%\_nis.%' OR filename LIKE '%\_interface\_relay.%' OR filename LIKE '%necron\_tunnel.rgd%' OR filename LIKE '%\_exarch\_council.%' OR filename LIKE '%\_dark\_reapers\_base.%' OR filename LIKE '%eldar\_deep\_strike\_building.rgd%' OR filename LIKE '%ork\_deep\_strike\_building.rgd%' OR filename LIKE '%\_caravel\_ai.rgd%' OR filename LIKE '%sisters\_tanktrap\_ai.rgd%' OR filename LIKE '%sisters\_hq\_ktgm.rgd%' OR filename LIKE '%tau\_squad\_slave\_murdered%' OR filename LIKE '%single\_player\_only%' OR filename LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR filename LIKE '%advance\_sp%' OR filename LIKE '%night\_lords\_hq\_aep%' OR filename LIKE '%\_ktgm.rgd%' OR filename LIKE '%salamander\_hq\_aep.rgd%' OR filename LIKE '%\_no\_limit.rgd%');
DELETE FROM weapons WHERE mod_id = mod_id_p AND (filename LIKE '%\_dxp3.%' OR filename LIKE '%\_dxp3.%' OR filename LIKE '%sp\_eldar\_%' OR filename LIKE '%\_sp\_%' OR filename LIKE '%\_sp.%' OR filename LIKE '%\_nis.%' OR filename LIKE '%\_interface\_relay.%' OR filename LIKE '%necron\_tunnel.rgd%' OR filename LIKE '%\_exarch\_council.%' OR filename LIKE '%\_dark\_reapers\_base.%' OR filename LIKE '%eldar\_deep\_strike\_building.rgd%' OR filename LIKE '%ork\_deep\_strike\_building.rgd%' OR filename LIKE '%\_caravel\_ai.rgd%' OR filename LIKE '%sisters\_tanktrap\_ai.rgd%' OR filename LIKE '%sisters\_hq\_ktgm.rgd%' OR filename LIKE '%tau\_squad\_slave\_murdered%' OR filename LIKE '%single\_player\_only%' OR filename LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR filename LIKE '%advance\_sp%');
DELETE FROM researches WHERE mod_id = mod_id_p AND (filename LIKE '%\_dxp3.%' OR filename LIKE '%\_dxp3.%' OR filename LIKE '%sp\_eldar\_%' OR filename LIKE '%\_sp\_%' OR filename LIKE '%\_sp.%' OR filename LIKE '%\_nis.%' OR filename LIKE '%\_interface\_relay.%' OR filename LIKE '%necron\_tunnel.rgd%' OR filename LIKE '%\_exarch\_council.%' OR filename LIKE '%\_dark\_reapers\_base.%' OR filename LIKE '%eldar\_deep\_strike\_building.rgd%' OR filename LIKE '%ork\_deep\_strike\_building.rgd%' OR filename LIKE '%\_caravel\_ai.rgd%' OR filename LIKE '%sisters\_tanktrap\_ai.rgd%' OR filename LIKE '%sisters\_hq\_ktgm.rgd%' OR filename LIKE '%tau\_squad\_slave\_murdered%' OR filename LIKE '%single\_player\_only%' OR filename LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR filename LIKE '%advance\_sp%');
DELETE FROM research_modifiers USING researches WHERE researches.mod_id = 3 AND researches.id = research_id AND (target LIKE '%\_dxp3%' OR target LIKE '%\_dxp3%' OR target LIKE '%sp\_eldar\_%' OR target LIKE '%\_sp\_%' OR target LIKE '%\_sp.%' OR target LIKE '%\_nis.%' OR target LIKE '%\_interface\_relay.%' OR target LIKE '%necron\_tunnel.rgd%' OR target LIKE '%\_exarch\_council.%' OR target LIKE '%\_dark\_reapers\_base.%' OR target LIKE '%eldar\_deep\_strike\_building.rgd%' OR target LIKE '%ork\_deep\_strike\_building.rgd%' OR target LIKE '%\_caravel\_ai.rgd%' OR target LIKE '%sisters\_tanktrap\_ai.rgd%' OR target LIKE '%sisters\_hq\_ktgm.rgd%' OR target LIKE '%tau\_squad\_slave\_murdered%' OR target LIKE '%single\_player\_only%' OR target LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR target LIKE '%advance\_sp%');
DELETE FROM building_addons USING buildings WHERE buildings.mod_id = 3 AND buildings.id = building_addons.building_id AND (building_addons.filename LIKE '%\_dxp3%' OR building_addons.filename LIKE '%\_dxp3%' OR building_addons.filename LIKE '%sp\_eldar\_%' OR building_addons.filename LIKE '%\_sp\_%' OR building_addons.filename LIKE '%\_sp.%' OR building_addons.filename LIKE '%\_nis.%' OR building_addons.filename LIKE '%\_interface\_relay.%' OR building_addons.filename LIKE '%necron\_tunnel.rgd%' OR building_addons.filename LIKE '%\_exarch\_council.%' OR building_addons.filename LIKE '%\_dark\_reapers\_base.%' OR building_addons.filename LIKE '%eldar\_deep\_strike\_building.rgd%' OR building_addons.filename LIKE '%ork\_deep\_strike\_building.rgd%' OR building_addons.filename LIKE '%\_caravel\_ai.rgd%' OR building_addons.filename LIKE '%sisters\_tanktrap\_ai.rgd%' OR building_addons.filename LIKE '%sisters\_hq\_ktgm.rgd%' OR building_addons.filename LIKE '%tau\_squad\_slave\_murdered%' OR building_addons.filename LIKE '%single\_player\_only%' OR building_addons.filename LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR building_addons.filename LIKE '%advance\_sp%' OR building_addons.filename LIKE '%addon\_necron\_hq\_3%');
DELETE FROM abilities WHERE mod_id = mod_id_p AND (filename LIKE '%\_dxp3.%' OR filename LIKE '%\_dxp3.%' OR filename LIKE '%sp\_eldar\_%' OR filename LIKE '%\_sp\_%' OR filename LIKE '%\_sp.%' OR filename LIKE '%\_nis.%' OR filename LIKE '%\_interface\_relay.%' OR filename LIKE '%necron\_tunnel.rgd%' OR filename LIKE '%\_exarch\_council.%' OR filename LIKE '%\_dark\_reapers\_base.%' OR filename LIKE '%eldar\_deep\_strike\_building.rgd%' OR filename LIKE '%ork\_deep\_strike\_building.rgd%' OR filename LIKE '%\_caravel\_ai.rgd%' OR filename LIKE '%sisters\_tanktrap\_ai.rgd%' OR filename LIKE '%sisters\_hq\_ktgm.rgd%' OR filename LIKE '%tau\_squad\_slave\_murdered%' OR filename LIKE '%single\_player\_only%' OR filename LIKE '%space\_marine\_drop\_pod\_building.rgd%' OR filename LIKE '%advance\_sp%');
$$;

View File

@ -0,0 +1,9 @@
CREATE OR REPLACE PROCEDURE delete_mod_data(mod_id_p bigint)
LANGUAGE SQL
AS $$
DELETE FROM units WHERE mod_id = mod_id_p;
DELETE FROM buildings WHERE mod_id = mod_id_p;
DELETE FROM weapons WHERE mod_id = mod_id_p;
DELETE FROM researches WHERE mod_id = mod_id_p;
DELETE FROM abilities WHERE mod_id = mod_id_p;
$$;

View File

@ -0,0 +1,94 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add abilities table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "abilities",
"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": "cost_requisition",
"type": "number"
}
},{
"column": {
"name": "cost_power",
"type": "number"
}
},{
"column": {
"name": "cost_faith",
"type": "number"
}
},
{
"column": {
"name": "cost_souls",
"type": "number"
}
},{
"column": {
"name": "icon",
"type": "varchar(128)"
}
},{
"column": {
"name": "mod_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "mod_id",
"baseTableName": "abilities",
"constraintName": "fk_abilities_mods",
"referencedColumnNames": "id",
"referencedTableName": "mods"
}
}
]
}
}
]
}

View File

@ -0,0 +1,66 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add buildings_researches table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "buildings_researches",
"columns": [
{
"column": {
"name": "building_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addUniqueConstraint": {
"columnNames": "building_id, research_id",
"constraintName": "uc_buildings_researches_research_id_building_id",
"tableName": "buildings_researches"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "building_id",
"baseTableName": "buildings_researches",
"constraintName": "fk_buildings_buildings_researches",
"referencedColumnNames": "id",
"referencedTableName": "buildings",
"onDelete": "CASCADE"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "buildings_researches",
"constraintName": "fk_researches_buildings_researches",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,69 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add research_modifiers table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "research_modifiers",
"columns": [
{
"column": {
"name": "id",
"type": "int",
"autoIncrement": true,
"constraints": {
"primaryKey": true,
"nullable": false
}
}
},{
"column": {
"name": "reference",
"type": "varchar(255)"
}
},{
"column": {
"name": "usage_type",
"type": "varchar(255)"
}
},{
"column": {
"name": "target",
"type": "varchar(255)"
}
},{
"column": {
"name": "value",
"type": "number"
}
},{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "research_modifiers",
"constraintName": "fk_researches_research_modifiers",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,59 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add research_requirements table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "research_requirements",
"columns": [
{
"column": {
"name": "id",
"type": "int",
"autoIncrement": true,
"constraints": {
"primaryKey": true,
"nullable": false
}
}
},{
"column": {
"name": "reference",
"type": "varchar(255)"
}
},{
"column": {
"name": "value",
"type": "varchar(255)"
}
},{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "research_requirements",
"constraintName": "fk_research_requirements_researches",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,99 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add researches table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "researches",
"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": "cost_requisition",
"type": "number"
}
},{
"column": {
"name": "cost_power",
"type": "number"
}
},{
"column": {
"name": "cost_faith",
"type": "number"
}
},{
"column": {
"name": "cost_souls",
"type": "number"
}
},{
"column": {
"name": "cost_time",
"type": "int"
}
},{
"column": {
"name": "mod_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "icon",
"type": "varchar(128)"
}
}
]
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "mod_id",
"baseTableName": "researches",
"constraintName": "fk_researches_mods",
"referencedColumnNames": "id",
"referencedTableName": "mods"
}
}
]
}
}
]
}

View File

@ -0,0 +1,66 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add researches_affected_buildings table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "researches_affected_buildings",
"columns": [
{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "building_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addUniqueConstraint": {
"columnNames": "research_id, building_id",
"constraintName": "uc_researches_affected_buildings_research_id_building_id",
"tableName": "researches_affected_buildings"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "researches_affected_buildings",
"constraintName": "fk_researches_researches_affected_buildings",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "building_id",
"baseTableName": "researches_affected_buildings",
"constraintName": "fk_buildings_researches_affected_buildings",
"referencedColumnNames": "id",
"referencedTableName": "buildings",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,66 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add researches_affected_sergeants table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "researches_affected_sergeants",
"columns": [
{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "sergeant_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addUniqueConstraint": {
"columnNames": "research_id, sergeant_id",
"constraintName": "uc_researches_affected_sergeants_research_id_sergeant_id",
"tableName": "researches_affected_sergeants"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "researches_affected_sergeants",
"constraintName": "fk_researches_researches_affected_sergeants",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "sergeant_id",
"baseTableName": "researches_affected_sergeants",
"constraintName": "fk_sergeants_researches_affected_sergeants",
"referencedColumnNames": "id",
"referencedTableName": "sergeants",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,66 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add researches_affected_units table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "researches_affected_units",
"columns": [
{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "unit_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addUniqueConstraint": {
"columnNames": "research_id, unit_id",
"constraintName": "uc_researches_affected_units_research_id_unit_id",
"tableName": "researches_affected_units"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "researches_affected_units",
"constraintName": "fk_researches_researches_affected_units",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "unit_id",
"baseTableName": "researches_affected_units",
"constraintName": "fk_units_researches_affected_units",
"referencedColumnNames": "id",
"referencedTableName": "units",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,66 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add researches_affected_weapons table",
"author": "anibus",
"changes": [
{
"createTable": {
"tableName": "researches_affected_weapons",
"columns": [
{
"column": {
"name": "research_id",
"type": "int",
"constraints": {
"nullable": false
}
}
},
{
"column": {
"name": "weapon_id",
"type": "int",
"constraints": {
"nullable": false
}
}
}
]
}
},
{
"addUniqueConstraint": {
"columnNames": "research_id, weapon_id",
"constraintName": "uc_researches_affected_weapons_research_id_weapon_id",
"tableName": "researches_affected_weapons"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "research_id",
"baseTableName": "researches_affected_weapons",
"constraintName": "fk_researches_researches_affected_weapons",
"referencedColumnNames": "id",
"referencedTableName": "researches",
"onDelete": "CASCADE"
}
},
{
"addForeignKeyConstraint":
{
"baseColumnNames": "weapon_id",
"baseTableName": "researches_affected_weapons",
"constraintName": "fk_weapons_researches_affected_weapons",
"referencedColumnNames": "id",
"referencedTableName": "weapons",
"onDelete": "CASCADE"
}
}
]
}
}
]
}

View File

@ -0,0 +1,25 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add mod_id column to sergeants",
"author": "anibus",
"changes": [
{
"addColumn": {
"columns": [
{
"column": {
"name": "mod_id",
"type": "int"
}
}
],
"tableName": "sergeants"
}
}
]
}
}
]
}

View File

@ -0,0 +1,36 @@
{
"databaseChangeLog": [
{
"changeSet": {
"id": "Add filename_unit column to unit and rename filename to filename_squad",
"author": "anibus",
"changes": [
{
"addColumn": {
"columns": [
{
"column": {
"name": "filename_unit",
"type": "varchar(255)",
"value": "",
"constraints": {
"nullable": false
}
}
}
],
"tableName": "units"
}
},
{
"renameColumn": {
"newColumnName": "filename_squad",
"oldColumnName": "filename",
"tableName": "units"
}
}
]
}
}
]
}

View File

@ -107,6 +107,67 @@
"include": { "include": {
"file": "db/0.0.2/data/armor_types.json" "file": "db/0.0.2/data/armor_types.json"
} }
},
{
"include": {
"file": "db/0.0.3/schema/researches.json"
}
},{
"include": {
"file": "db/0.0.3/schema/research_requirements.json"
}
},{
"include": {
"file": "db/0.0.3/schema/research_modifiers.json"
}
},{
"include": {
"file": "db/0.0.3/schema/abilities.json"
}
},{
"include": {
"file": "db/0.0.3/schema/buildings_researches.json"
}
},{
"include": {
"file": "db/0.0.3/schema/researches_affected_units.json"
}
},{
"include": {
"file": "db/0.0.3/schema/researches_affected_sergeants.json"
}
},{
"include": {
"file": "db/0.0.3/schema/researches_affected_buildings.json"
}
},{
"include": {
"file": "db/0.0.3/schema/researches_affected_weapons.json"
}
},
{
"include": {
"file": "db/0.0.3/procedures/delete_mod_data_procedure.sql"
}
},
{
"include": {
"file": "db/0.0.3/schema/units.json"
}
},
{
"include": {
"file": "db/0.0.3/data/armor_types.json"
}
},{
"include": {
"file": "db/0.0.3/procedures/delete_campaign_entities.sql"
}
},
{
"include": {
"file": "db/0.0.3/schema/sergeants.json"
}
} }
] ]
} }

View File

@ -18,33 +18,6 @@ class BuildingRgdExtractServiceTest {
val iconService = mockk<IconsService>() val iconService = mockk<IconsService>()
val addonRgdExtractService = mockk<BuildingAddonRgdExtractService>() val addonRgdExtractService = mockk<BuildingAddonRgdExtractService>()
val buildingRgdExtractService = BuildingRgdExtractService(modAttribPathService, iconService, addonRgdExtractService)
val rgdService = RgdParserService() 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)
}
} }