diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4b2122a..0000000 --- a/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties -*.apk -*.aab -*.ap_ -*.dex -*.class -*.log -*.hprof -*.iws -*.ipr -.idea/ -.gradle/ -build/ -app/build/ -app/release/ -local.properties -*.keystore -!gradle-wrapper.jar -/cxx/ -*.navigation/ -*.orig -*.tflite -__pycache__/ -*.pyc diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/MainActivity.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/MainActivity.kt index 79c49f4..4371cee 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/MainActivity.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/MainActivity.kt @@ -33,6 +33,21 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextAlign import ca.jonathanmccaffrey.igp.di.ServiceLocator +import ca.jonathanmccaffrey.igp.ui.screens.AboutScreen +import ca.jonathanmccaffrey.igp.ui.screens.BuildCalculatorScreen +import ca.jonathanmccaffrey.igp.ui.screens.ContactScreen +import ca.jonathanmccaffrey.igp.ui.screens.DataCollectionScreen +import ca.jonathanmccaffrey.igp.ui.screens.DataTablesScreen +import ca.jonathanmccaffrey.igp.ui.screens.DatabaseScreen +import ca.jonathanmccaffrey.igp.ui.screens.EconomyComparisonScreen +import ca.jonathanmccaffrey.igp.ui.screens.HarassCalculatorScreen +import ca.jonathanmccaffrey.igp.ui.screens.HomeScreen +import ca.jonathanmccaffrey.igp.ui.screens.MemoryTesterScreen +import ca.jonathanmccaffrey.igp.ui.screens.NotesScreen +import ca.jonathanmccaffrey.igp.ui.screens.PermissionsScreen +import ca.jonathanmccaffrey.igp.ui.screens.RoadMapScreen +import ca.jonathanmccaffrey.igp.ui.screens.StorageScreen +import ca.jonathanmccaffrey.igp.ui.screens.StreamsScreen import ca.jonathanmccaffrey.igp.ui.theme.IGPTheme class MainActivity : ComponentActivity() { @@ -74,6 +89,7 @@ sealed class ScreenContent { data object DataTables : ScreenContent() data object Permissions : ScreenContent() data object DataCollection : ScreenContent() + data object Storage : ScreenContent() data object Streams : ScreenContent() data object Contact : ScreenContent() data object RoadMap : ScreenContent() @@ -88,7 +104,7 @@ fun IGPApp() { listOf(ScreenContent.Database), listOf(ScreenContent.BuildCalculator, ScreenContent.EconomyComparison, ScreenContent.MemoryTester, ScreenContent.HarassCalculator, ScreenContent.DataTables), listOf(ScreenContent.Notes), - listOf(ScreenContent.About, ScreenContent.Permissions, ScreenContent.DataCollection, ScreenContent.Streams, ScreenContent.Contact, ScreenContent.RoadMap), + listOf(ScreenContent.About, ScreenContent.Permissions, ScreenContent.Storage, ScreenContent.DataCollection, ScreenContent.Streams, ScreenContent.Contact, ScreenContent.RoadMap), ) var currentScreen by remember { mutableStateOf(ScreenContent.Home) } @@ -116,70 +132,24 @@ fun IGPApp() { } }, ) { innerPadding -> - when (currentScreen) { - is ScreenContent.Home -> HomeScreen(Modifier.padding(innerPadding)) - is ScreenContent.Database -> DatabaseScreen(Modifier.padding(innerPadding)) - is ScreenContent.BuildCalculator -> BuildCalculatorScreen(Modifier.padding(innerPadding)) - is ScreenContent.EconomyComparison -> EconomyComparisonScreen(Modifier.padding(innerPadding)) - is ScreenContent.MemoryTester -> MemoryTesterScreen(Modifier.padding(innerPadding)) - is ScreenContent.Notes -> NotesScreen(Modifier.padding(innerPadding)) - is ScreenContent.About -> AboutScreen(Modifier.padding(innerPadding)) - is ScreenContent.HarassCalculator -> HarassCalculatorScreen(Modifier.padding(innerPadding)) - is ScreenContent.DataTables -> DataTablesScreen(Modifier.padding(innerPadding)) - is ScreenContent.Permissions -> PermissionsScreen(Modifier.padding(innerPadding)) - is ScreenContent.DataCollection -> DataCollectionScreen(Modifier.padding(innerPadding)) - is ScreenContent.Streams -> StreamsScreen(Modifier.padding(innerPadding)) - is ScreenContent.Contact -> ContactScreen(Modifier.padding(innerPadding)) - is ScreenContent.RoadMap -> RoadMapScreen(Modifier.padding(innerPadding)) + Box(modifier = Modifier.padding(innerPadding)) { + when (currentScreen) { + is ScreenContent.Home -> HomeScreen() + is ScreenContent.Database -> DatabaseScreen() + is ScreenContent.BuildCalculator -> BuildCalculatorScreen() + is ScreenContent.EconomyComparison -> EconomyComparisonScreen() + is ScreenContent.MemoryTester -> MemoryTesterScreen() + is ScreenContent.Notes -> NotesScreen() + is ScreenContent.About -> AboutScreen() + is ScreenContent.HarassCalculator -> HarassCalculatorScreen() + is ScreenContent.DataTables -> DataTablesScreen() + is ScreenContent.Permissions -> PermissionsScreen() + is ScreenContent.DataCollection -> DataCollectionScreen() + is ScreenContent.Storage -> StorageScreen() + is ScreenContent.Streams -> StreamsScreen() + is ScreenContent.Contact -> ContactScreen() + is ScreenContent.RoadMap -> RoadMapScreen() + } } } } - -@Composable -fun PlaceholderScreen(name: String, modifier: Modifier = Modifier) { - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text(text = name, textAlign = TextAlign.Center) - } -} - -@Composable -fun HomeScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Home", modifier) - -@Composable -fun DatabaseScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Database", modifier) - -@Composable -fun BuildCalculatorScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Build Calculator", modifier) - -@Composable -fun EconomyComparisonScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Economy Comparison", modifier) - -@Composable -fun MemoryTesterScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Memory Tester", modifier) - -@Composable -fun NotesScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Notes", modifier) - -@Composable -fun AboutScreen(modifier: Modifier = Modifier) = PlaceholderScreen("About", modifier) - -@Composable -fun HarassCalculatorScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Harass Calculator", modifier) - -@Composable -fun DataTablesScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Data Tables", modifier) - -@Composable -fun PermissionsScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Permissions", modifier) - -@Composable -fun DataCollectionScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Data Collection", modifier) - -@Composable -fun StreamsScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Streams", modifier) - -@Composable -fun ContactScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Contact", modifier) - -@Composable -fun RoadMapScreen(modifier: Modifier = Modifier) = PlaceholderScreen("Road Map", modifier) diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/EntityModel.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/EntityModel.kt index 45b46fb..b163630 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/EntityModel.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/EntityModel.kt @@ -69,22 +69,22 @@ class EntityModel( } fun info(): EntityInfoModel = - entityParts.find { it is EntityInfoModel } as EntityInfoModel + (entityParts.find { it is EntityInfoModel } as? EntityInfoModel) ?: EntityInfoModel() fun supply(): EntitySupplyModel? = entityParts.find { it is EntitySupplyModel } as? EntitySupplyModel fun tier(): EntityTierModel = - entityParts.find { it is EntityTierModel } as EntityTierModel + (entityParts.find { it is EntityTierModel } as? EntityTierModel) ?: EntityTierModel() fun production(): EntityProductionModel? = entityParts.find { it is EntityProductionModel } as? EntityProductionModel fun movement(): EntityMovementModel = - entityParts.find { it is EntityMovementModel } as EntityMovementModel + (entityParts.find { it is EntityMovementModel } as? EntityMovementModel) ?: EntityMovementModel() fun vitality(): EntityVitalityModel = - entityParts.find { it is EntityVitalityModel } as EntityVitalityModel + (entityParts.find { it is EntityVitalityModel } as? EntityVitalityModel) ?: EntityVitalityModel() fun requirements(): List = entityParts.filterIsInstance() diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/EntityData.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/EntityData.kt index 24a6125..93a3901 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/EntityData.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/EntityData.kt @@ -1,148 +1,3340 @@ -package ca.jonathanmccaffrey.igp.data.models.entity.data +package ca.jonathanmccaffrey.igp.data.models.entity.data import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityFactionModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityHarvestModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityInfoModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityProductionModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntitySupplyModel -import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityTierModel -import ca.jonathanmccaffrey.igp.data.models.entity.types.DescriptiveType -import ca.jonathanmccaffrey.igp.data.models.entity.types.ResourceType +import ca.jonathanmccaffrey.igp.data.models.entity.types.* +import ca.jonathanmccaffrey.igp.data.models.entity.parts.* object EntityData { - private var _cache: Map? = null - - private fun getAbilityData(): Map { - // TODO: Convert from EntityData.Ability.cs - contains abilities like RadiantWard, Windstep, etc. - return emptyMap() + private val _data: Map by lazy { + val data = mutableMapOf() + data.putAll(getArmyData()) + data.putAll(getAbilityData()) + data.putAll(getBuildingData()) + data.putAll(getImmortalData()) + data.putAll(getMiscData()) + data.putAll(getPassiveData()) + data.putAll(getResearchData()) + data } + fun get(): Map = _data + private fun getArmyData(): Map { - // TODO: Convert from EntityData.Army.cs - contains army units, vanguards, etc. - return emptyMap() - } - - private fun getBuildingData(): Map { - // TODO: Convert from EntityData.Building.cs - contains buildings and upgrades - return emptyMap() - } - - private fun getImmortalData(): Map { - // TODO: Convert from EntityData.Immortal.cs - contains immortal entities - return emptyMap() - } - - private fun getMiscData(): Map { - // TODO: Convert from EntityData.Misc.cs - contains misc entities like rocks, teapots - return emptyMap() - } - - private fun getPassiveData(): Map { - // TODO: Convert from EntityData.Passive.cs - contains passive entities - return emptyMap() - } - - private fun getResearchData(): Map { - // TODO: Convert from EntityData.Research.cs - contains research/upgrade entities - return emptyMap() - } - - private fun getEssentialEntities(): Map { - val bastion = EntityModel(IdsEntity.STARTING_Bastion, EntityType.Building).apply { - addPart(EntityInfoModel().apply { name = "Bastion"; descriptive = DescriptiveType.Stronghold }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Neutral }) - addPart(EntityProductionModel().apply { buildTime = 0 }) - addPart(EntityTierModel()) - addPart(EntitySupplyModel().apply { grants = 20; takes = 0 }) - } - - val townHallAru = EntityModel(IdsEntity.STARTING_TownHall_Aru, EntityType.Building).apply { - addPart(EntityInfoModel().apply { name = "Town Hall (Aru)"; descriptive = DescriptiveType.TownHall_Starting }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) - addPart(EntityProductionModel().apply { buildTime = 0 }) - addPart(EntityTierModel()) - addPart(EntityHarvestModel().apply { slots = 0f; harvestedPerInterval = 0f }) - } - - val townHallQRath = EntityModel(IdsEntity.STARTING_TownHall_QRath, EntityType.Building).apply { - addPart(EntityInfoModel().apply { name = "Town Hall (QRath)"; descriptive = DescriptiveType.TownHall_Starting }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) - addPart(EntityProductionModel().apply { buildTime = 0 }) - addPart(EntityTierModel()) - addPart(EntityHarvestModel().apply { slots = 0f; harvestedPerInterval = 0f }) - } - - val acropolis = EntityModel(IdsEntity.BUILDING_Acropolis, EntityType.Building).apply { - addPart(EntityInfoModel().apply { name = "Acropolis"; descriptive = DescriptiveType.TownHall_Starting }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) - addPart(EntityProductionModel().apply { buildTime = 30 }) - addPart(EntityTierModel()) - addPart(EntitySupplyModel().apply { grants = 20; takes = 0 }) - } - - val groveHeart = EntityModel(IdsEntity.BUILDING_GroveHeart, EntityType.Building).apply { - addPart(EntityInfoModel().apply { name = "Grove Heart"; descriptive = DescriptiveType.TownHall_Starting }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) - addPart(EntityProductionModel().apply { buildTime = 30 }) - addPart(EntityTierModel()) - addPart(EntitySupplyModel().apply { grants = 20; takes = 0 }) - } - - val mining2QRath = EntityModel(IdsEntity.BUPGRADE_MiningLevel2_QRath, EntityType.Building_Upgrade).apply { - addPart(EntityInfoModel().apply { name = "Mining Level 2 (QRath)"; descriptive = DescriptiveType.Upgrade }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) - addPart(EntityProductionModel().apply { buildTime = 10; alloy = 25; ether = 25 }) - addPart(EntityTierModel()) - } - - val mining2Aru = EntityModel(IdsEntity.BUPGRADE_MiningLevel2_Aru, EntityType.Building_Upgrade).apply { - addPart(EntityInfoModel().apply { name = "Mining Level 2 (Aru)"; descriptive = DescriptiveType.Upgrade }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) - addPart(EntityProductionModel().apply { buildTime = 10; alloy = 25; ether = 25 }) - addPart(EntityTierModel()) - } - - val workerMote = EntityModel(IdsEntity.WORKER_Mote, EntityType.Army).apply { - addPart(EntityInfoModel().apply { name = "Mote"; descriptive = DescriptiveType.Worker }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) - addPart(EntityProductionModel().apply { buildTime = 8; alloy = 2.5f.toInt() }) - addPart(EntityTierModel()) - addPart(EntityHarvestModel().apply { resource = ResourceType.Alloy; slots = 1f; harvestedPerInterval = 2f; requiresWorker = true; totalAmount = 9999 }) - } - - val workerSymbiote = EntityModel(IdsEntity.WORKER_Symbiote, EntityType.Army).apply { - addPart(EntityInfoModel().apply { name = "Symbiote"; descriptive = DescriptiveType.Worker }) - addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) - addPart(EntityProductionModel().apply { buildTime = 8; alloy = 2.5f.toInt() }) - addPart(EntityTierModel()) - addPart(EntityHarvestModel().apply { resource = ResourceType.Alloy; slots = 1f; harvestedPerInterval = 2f; requiresWorker = true; totalAmount = 9999 }) - } - return mapOf( - IdsEntity.STARTING_Bastion to bastion, - IdsEntity.STARTING_TownHall_Aru to townHallAru, - IdsEntity.STARTING_TownHall_QRath to townHallQRath, - IdsEntity.BUILDING_Acropolis to acropolis, - IdsEntity.BUILDING_GroveHeart to groveHeart, - IdsEntity.BUPGRADE_MiningLevel2_QRath to mining2QRath, - IdsEntity.BUPGRADE_MiningLevel2_Aru to mining2Aru, - IdsEntity.WORKER_Mote to workerMote, - IdsEntity.WORKER_Symbiote to workerSymbiote + IdsEntity.VANGUARD_Zentari_Orzum to EntityModel(IdsEntity.VANGUARD_Zentari_Orzum, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Zentari" + descriptive = DescriptiveType.Frontliner + description = """ + Ground unit. Attacks ground targets. Ranged attack in Hallowed Ground. + """.trimIndent() + flavorText = """ + "Through our faith and duty bid us part, know that I will be here to protect you for the + rest of yours days."
+ —Zentari Lord Orz'Abin + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1f }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Sipari; immortalId = IdsEntity.IMMORTAL_Orzum }) + .addPart(EntityProductionModel().apply { alloy = 100; buildTime = 20; producedBy = IdsEntity.BUILDING_LegionHall }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { health = 180; defenseLayer = 100; armor = ArmourType.Light }) + .addPart(EntityWeaponModel().apply { damage = 26; range = 100; attacksPerSecond = 0.699f; targets = TargetType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 26; range = 300; attacksPerSecond = 0.699f; targets = TargetType.Ground }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityMovementModel().apply { speed = 380f; movement = MovementType.Ground }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_FaithCastBlades }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_IconOfKhastEem }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_IconOfKhastEem }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_FaithCastBlades }), + + IdsEntity.VANGUARD_Sceptre_Orzum to EntityModel(IdsEntity.VANGUARD_Sceptre_Orzum, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Sceptre" + descriptive = DescriptiveType.Skirmisher + description = """ + Air unit. Attacks ground targets. Anti-ground specialist.
+ Empowered attack does bonus damage and Burns adjacent enemies, dealing damage over time. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 200; ether = 175; buildTime = 32; producedBy = IdsEntity.BUILDING_Angelarium }) + .addPart(EntityVitalityModel().apply { health = 435; defenseLayer = 150; armor = ArmourType.Heavy }) + .addPart(EntitySupplyModel().apply { takes = 6 }) + .addPart(EntityWeaponModel().apply { + damage = 35; mediumDamage = 40; heavyDamage = 45; range = 450; secondsBetweenAttacks = 2.1f + targets = TargetType.Ground + }) + .addPart(EntityWeaponModel().apply { + damage = 125; range = 450; secondsBetweenAttacks = 2f + targets = TargetType.Ground; hasSplash = true + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Warden; immortalId = IdsEntity.IMMORTAL_Orzum }) + .addPart(EntityMovementModel().apply { speed = 340f; movement = MovementType.Air }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_RegentsWrath }), + + IdsEntity.VANGUARD_Saoshin_Ajari to EntityModel(IdsEntity.VANGUARD_Saoshin_Ajari, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Saoshin" + descriptive = DescriptiveType.Support + description = """ + Ground Spell Caster. Casts a healing/utility spell. Protector. + """.trimIndent() + flavorText = """ + "You will hold fast the lost, dragging them to salvation. For only + your hearts can hold the boundless love of Q'rath"
+ —Ajari, Arash of Deliverance + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Magi; immortalId = IdsEntity.IMMORTAL_Ajari }) + .addPart(EntityProductionModel().apply { alloy = 75; ether = 75; buildTime = 28; producedBy = IdsEntity.BUILDING_LegionHall }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { health = 210; defenseLayer = 105; armor = ArmourType.Light; isEtheric = true }) + .addPart(EntityMovementModel().apply { speed = 460f; movement = MovementType.Ground }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Production_Building + }) + .addPart(EntityWeaponModel().apply { damage = 18; range = 400; secondsBetweenAttacks = 1.4f; targets = TargetType.Ground }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_Intervention }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Invervention }), + + IdsEntity.VANGUARD_ArkMother_Ajari to EntityModel(IdsEntity.VANGUARD_ArkMother_Ajari, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Ark Mother" + descriptive = DescriptiveType.Support + description = """ + Ground Spellcaster. Casts a utility spell. Protector.
+ Creates Hallowed Ground when still for a few seconds. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Hallower; immortalId = IdsEntity.IMMORTAL_Ajari }) + .addPart(EntityProductionModel().apply { alloy = 125; ether = 100; buildTime = 32; producedBy = IdsEntity.BUILDING_SoulFoundry }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityVitalityModel().apply { energy = 100; health = 150; defenseLayer = 150; armor = ArmourType.Medium; isEtheric = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Research_Building + }) + .addPart(EntityMovementModel().apply { speed = 390f; movement = MovementType.Hover }) + .addPart(EntityWeaponModel().apply { damage = 25; range = 1100; secondsBetweenAttacks = 2.5f; targets = TargetType.Ground }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_OrdainedPassage }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowingRites }), + + IdsEntity.VANGUARD_Incubator_Mala to EntityModel(IdsEntity.VANGUARD_Incubator_Mala, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Incubator" + descriptive = DescriptiveType.Support + description = """ + Ground unit. Can only attack ground. Mala Vanguard. Replaces Underspine. + """.trimIndent() + flavorText = """ + "We cannot understand their pain when they lose their motherhood, nor can we judge their measures to regain it."
+ —Atzlan, to Om'loch + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Underspine; immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityProductionModel().apply { alloy = 75; ether = 100; buildTime = 28; producedBy = IdsEntity.BUILDING_AmberWomb }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityVitalityModel().apply { energy = 150; health = 180; defenseLayer = 40; armor = ArmourType.Medium; isEtheric = false }) + .addPart(EntityMovementModel().apply { speed = 340f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 16; range = 700; secondsBetweenAttacks = 1.65f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_ProjectileGestation }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_FallenHarvest }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_Hematoma }), + + IdsEntity.VANGUARD_DreadSister_Mala to EntityModel(IdsEntity.VANGUARD_DreadSister_Mala, EntityType.Army) + .addPart(EntityInfoModel().apply { name = "Dread Sister"; descriptive = DescriptiveType.EliteCaster }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_RedSeer; immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityProductionModel().apply { alloy = 60; ether = 150; buildTime = 36; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { + energy = 100; health = 90; defenseLayer = 110; armor = ArmourType.Light + defense = DefenseType.Overgrowth; isEtheric = false + }) + .addPart(EntityMovementModel().apply { speed = 360f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 14; range = 750; secondsBetweenAttacks = 1.4f; targets = TargetType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 120 + mediumDamage = 160 + heavyDamage = 200 + range = 1400 + secondsBetweenAttacks = 4.0f + targets = TargetType.Ground + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_RootVice }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_BirthingStorm }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_DeployDreadSister }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_BirthingStorm }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_CastingFromBlood }), + + IdsEntity.VANGUARD_RootShepard_Atzlan to EntityModel(IdsEntity.VANGUARD_RootShepard_Atzlan, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Root Shepard" + descriptive = DescriptiveType.Skirmisher + description = "Can generate rootway." + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Ichor; immortalId = IdsEntity.IMMORTAL_Atzlan }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 0; buildTime = 24; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 125; defenseLayer = 100; armor = ArmourType.Medium; isEtheric = false }) + .addPart(EntityMovementModel().apply { speed = 540f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 15; mediumDamage = 12; heavyDamage = 9; range = 400; attacksPerSecond = 2.4f + targets = TargetType.Ground + }), + + IdsEntity.VANGUARD_Resinant_Atzlan to EntityModel(IdsEntity.VANGUARD_Resinant_Atzlan, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Resinant" + descriptive = DescriptiveType.ZoneControl + description = "Ground unit. Can only attack ground." + }) + .addPart(EntityTierModel().apply { tier = 2.5f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_BloodAnchor; immortalId = IdsEntity.IMMORTAL_Atzlan }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 125; buildTime = 32; producedBy = IdsEntity.BUILDING_AmberWomb }) + .addPart(EntitySupplyModel().apply { takes = 6 }) + .addPart(EntityVitalityModel().apply { health = 260; defenseLayer = 90; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 360f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 25; mediumDamage = 35; heavyDamage = 45; range = 800; secondsBetweenAttacks = 1.429f + targets = TargetType.Ground + }) + .addPart(EntityWeaponModel().apply { + damage = 85; mediumDamage = 100; heavyDamage = 115; range = 1000; secondsBetweenAttacks = 2.5f + minimumRange = 500 + targets = TargetType.Ground + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_ResinantSpeed }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_MobilizeAru }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_EngorgedArteries }), + + IdsEntity.VANGUARD_BoneStalker_Xol to EntityModel(IdsEntity.VANGUARD_BoneStalker_Xol, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Bone Stalker" + descriptive = DescriptiveType.Generalist + description = "Ground unit. Attacks ground/air targets. Stealthy." + flavorText = """ + "They will come in deathy silence, awaiting the Great Hunt. In time, the 'Bone-Takers' shall + be without equal."
+ —Xol, Prophecies of the Icta'nuat + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_MaskedHunter; immortalId = IdsEntity.IMMORTAL_Xol }) + .addPart(EntityProductionModel().apply { alloy = 50; ether = 0; buildTime = 16; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 2 }) + .addPart(EntityVitalityModel().apply { health = 85; defenseLayer = 10; armor = ArmourType.Light; isEtheric = false }) + .addPart(EntityMovementModel().apply { speed = 380f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 11; range = 400; attacksPerSecond = 1.02f; targets = TargetType.All }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Stalk }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Ambush }), + + IdsEntity.VANGUARD_WhiteWoodReaper_Xol to EntityModel(IdsEntity.VANGUARD_WhiteWoodReaper_Xol, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "White Wood Reaper" + descriptive = DescriptiveType.Skirmisher + description = "Ground unit. Attacks ground targets. Permanently Hidden." + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { replaceId = IdsEntity.UNIT_Bloodbound; immortalId = IdsEntity.IMMORTAL_Xol }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 80; buildTime = 28; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { + energy = 60; health = 80; defenseLayer = 40; defense = DefenseType.Overgrowth + armor = ArmourType.Medium; isEtheric = false + }) + .addPart(EntityMovementModel().apply { speed = 448f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 40; range = 100; attacksPerSecond = 1f + targets = TargetType.Ground + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_CastingFromBlood }), + + IdsEntity.WORKER_Mote to EntityModel(IdsEntity.WORKER_Mote, EntityType.Worker) + .addPart(EntityInfoModel().apply { name = "Mote"; descriptive = DescriptiveType.Worker }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 50; buildTime = 20 }) + .addPart(EntityVitalityModel().apply { health = 45; defenseLayer = 45; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 5; range = 50; attacksPerSecond = 1.887f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HarvestAlloy }), + + IdsEntity.UNIT_Sipari to EntityModel(IdsEntity.UNIT_Sipari, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Sipari"; descriptive = DescriptiveType.Frontliner + description = """ + Ground unit. Attacks ground targets. Durable melee fighter.
+ Gets Shields in Hallowed Ground.
+ """.trimIndent() + flavorText = """ + "From the ranks of the faithful shall the true-hearted step forward. They shall + be My spear, My shield, pointed forever outward against Wicked and Beast!"
+ —Holy Aros, "Utterances 16.9" + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1f }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "Z" }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Orzum; replacedById = IdsEntity.VANGUARD_Zentari_Orzum }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 75; buildTime = 20; producedBy = IdsEntity.BUILDING_LegionHall }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 210; defenseLayer = 135; armor = ArmourType.Light }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityMovementModel().apply { speed = 440f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 20; heavyDamage = 18; range = 180 + secondsBetweenAttacks = 1.43f; targets = TargetType.Ground + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_GreavesOfAhqar }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_FortifiedIcons }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_GreavesOfAhqar }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_FortifiedIcons }), + + IdsEntity.UNIT_Magi to EntityModel(IdsEntity.UNIT_Magi, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Magi"; descriptive = DescriptiveType.Support + description = """ + Ground Spellcaster. Casts healing/utility spells. Protector.
+ Deploy to get Shields and create Hallowed Ground. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 50; ether = 75; buildTime = 28; producedBy = IdsEntity.BUILDING_LegionHall }) + .addPart(EntitySupplyModel().apply { takes = 2 }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Ajari; replacedById = IdsEntity.VANGUARD_Saoshin_Ajari }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { energy = 100; health = 75; defenseLayer = 75; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 420f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 6; range = 600; secondsBetweenAttacks = 1.5f + targets = TargetType.All + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_DeployMagi }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_MobilizeQrath }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_MendingDecree }), + + IdsEntity.UNIT_Zephyr to EntityModel(IdsEntity.UNIT_Zephyr, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Zephyr" + descriptive = DescriptiveType.Generalist + description = """ + Ground unit. Attacks ground/air targets. Can teleport. + """.trimIndent() + flavorText = """ + Once named, the bound between zephyr and pilot cannot be broken. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 100; ether = 40; buildTime = 24; producedBy = IdsEntity.BUILDING_LegionHall }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_MonasteryOfIzur + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { health = 255; defenseLayer = 105; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 22; mediumDamage = 27; heavyDamage = 32; range = 500; secondsBetweenAttacks = 1.5f + targets = TargetType.All + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_WindStep }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_ZephyrRange }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_Windstep }), + + IdsEntity.UNIT_Dervish to EntityModel(IdsEntity.UNIT_Dervish, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Dervish" + descriptive = DescriptiveType.Skirmisher + description = """ + Ground unit. Attacks ground targets. Fragile and agile.
+ Can use Radiant Ward to lay mines. Has damage reduction. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 125; ether = 10; buildTime = 24; producedBy = IdsEntity.BUILDING_SoulFoundry }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { health = 180; defenseLayer = 150; armor = ArmourType.Medium }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityMovementModel().apply { speed = 500f; movement = MovementType.Hover }) + .addPart(EntityWeaponModel().apply { + damage = 10; lightDamage = 26; mediumDamage = 18; range = 350; secondsBetweenAttacks = 2.0f + targets = TargetType.Ground + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_SiroccoScript }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_RadiantWard }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_RadiantWard }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_SiroccoScript }), + + IdsEntity.UNIT_Absolver to EntityModel(IdsEntity.UNIT_Absolver, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Absolver" + descriptive = DescriptiveType.ZoneControl + description = """ + Ground unit. Attacks ground targets. Deploys for area damage. + """.trimIndent() + notes = """ + Has a higher DPS against structures when unmobilized. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 75; buildTime = 28; producedBy = IdsEntity.BUILDING_SoulFoundry }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityVitalityModel().apply { health = 230; defenseLayer = 150; armor = ArmourType.Medium }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 11; mediumDamage = 13; heavyDamage = 15; structureDamageBonus = 8; range = 800 + attacksPerSecond = 1.25f; targets = TargetType.Ground + }) + .addPart(EntityWeaponModel().apply { + damage = 6; mediumDamage = 7; heavyDamage = 8; range = 800; attacksPerSecond = 2.222f + targets = TargetType.Ground + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_DeployAbsolver }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_MobilizeQrath }) + .addPart(EntityPassiveModel().apply { name = "?"; description = "Hits multiple units when deployed." }), + + IdsEntity.UNIT_Castigator to EntityModel(IdsEntity.UNIT_Castigator, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Castigator"; descriptive = DescriptiveType.AirKiller + description = """ + Ground unit. Can attack air and ground. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 75; buildTime = 28; producedBy = IdsEntity.BUILDING_SoulFoundry }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityVitalityModel().apply { health = 300; defenseLayer = 150; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 390f; movement = MovementType.Ground }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityWeaponModel().apply { + damage = 20 + mediumDamage = 30 + heavyDamage = 40 + range = 800 + secondsBetweenAttacks = 1.42f + targets = TargetType.Air + hasSplash = true + }) + .addPart(EntityWeaponModel().apply { damage = 16; range = 500; secondsBetweenAttacks = 1.5f; targets = TargetType.Ground }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_RelicOfTheWrathfulGaze }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.PASSIVE_Maledictions }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_RelicOfTheWrathfulGaze }), + + IdsEntity.UNIT_Hallower to EntityModel(IdsEntity.UNIT_Hallower, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Hallower"; descriptive = DescriptiveType.Dislodger + description = """ + Ground Unit. Attack ground targets. Long-range attack. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Ajari; replacedById = IdsEntity.VANGUARD_ArkMother_Ajari }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 100; buildTime = 32; producedBy = IdsEntity.BUILDING_SoulFoundry }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { health = 100; defenseLayer = 130; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 335f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 60; mediumDamage = 80; heavyDamage = 100; range = 1300; attacksPerSecond = 0.143f + targets = TargetType.Ground + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedRuin }), + + IdsEntity.UNIT_Sentinel to EntityModel(IdsEntity.UNIT_Sentinel, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Sentinel" + descriptive = DescriptiveType.AirKiller + description = """ + Air unit. Attacks air targets. Anti-air specialist. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 75; buildTime = 28; producedBy = IdsEntity.BUILDING_Angelarium }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { health = 150; defenseLayer = 100; armor = ArmourType.Medium }) + .addPart(EntityMovementModel().apply { speed = 525f; movement = MovementType.Air }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityWeaponModel().apply { damage = 28; range = 500; attacksPerSecond = 0.714f; targets = TargetType.Air }), + + IdsEntity.UNIT_Throne to EntityModel(IdsEntity.UNIT_Throne, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Throne" + descriptive = DescriptiveType.Generalist + description = """ + Air unit. Attacks ground/air targets. Ultimate unit. + """.trimIndent() + flavorText = """ + The Godhead Aros left silvers of itself in them, and no sight is more fearsome + to its enemies. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 350; ether = 200; buildTime = 70; producedBy = IdsEntity.BUILDING_Angelarium }) + .addPart(EntitySupplyModel().apply { takes = 10 }) + .addPart(EntityVitalityModel().apply { health = 550; defenseLayer = 350; armor = ArmourType.Heavy }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BearerOfTheCrown + requirement = RequirementType.Research_Building + }) + .addPart(EntityMovementModel().apply { speed = 300f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { + damage = 120; range = 500; secondsBetweenAttacks = 2.5f + targets = TargetType.All + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_TitheBlades }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_TitheBlades }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_GodstoneBulwark }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_ThroneMovingShot }), + + IdsEntity.UNIT_Warden to EntityModel(IdsEntity.UNIT_Warden, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Warden" + descriptive = DescriptiveType.Skirmisher + description = """ + Air unit. Attacks ground targets. Anti-ground specialist. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Orzum; replacedById = IdsEntity.VANGUARD_Sceptre_Orzum }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 175; ether = 100; buildTime = 32; producedBy = IdsEntity.BUILDING_Angelarium }) + .addPart(EntitySupplyModel().apply { takes = 6 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { health = 375; defenseLayer = 100; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 480f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { damage = 32; range = 500; secondsBetweenAttacks = 1.8f; targets = TargetType.Ground }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_WingsOfTheKenLatir }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_WingsOfTheKenLatir }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_ExecutionRites }), + + IdsEntity.UNIT_SharU to EntityModel(IdsEntity.UNIT_SharU, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Shar'U" + descriptive = DescriptiveType.DamageCaster + }) + .addPart(EntityTierModel().apply { tier = 3.5f }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 110; ether = 250; buildTime = 55; producedBy = IdsEntity.BUILDING_Angelarium }) + .addPart(EntitySupplyModel().apply { takes = 6 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_EyeOfAros + requirement = RequirementType.Research_Building + }) + .addPart(EntityVitalityModel().apply { health = 300; defenseLayer = 155; energy = 150; isEtheric = true; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 360f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { + damage = 10; range = 650; secondsBetweenAttacks = 1.4f + targets = TargetType.All + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_Awestrike }), + + IdsEntity.WORKER_Symbiote to EntityModel(IdsEntity.WORKER_Symbiote, EntityType.Worker) + .addPart(EntityInfoModel().apply { name = "Symbiote"; descriptive = DescriptiveType.Worker }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 50; buildTime = 20 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_GroveHeart + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { health = 75; defenseLayer = 15; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Hover }) + .addPart(EntityWeaponModel().apply { damage = 8; range = 40; attacksPerSecond = 1.25f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HarvestAlloy }), + + IdsEntity.UNIT_MaskedHunter to EntityModel(IdsEntity.UNIT_MaskedHunter, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Masked Hunter" + descriptive = DescriptiveType.Generalist + description = """ + Ground Unit. Can attack ground and air. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1f }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "Z" }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Xol; replacedById = IdsEntity.VANGUARD_BoneStalker_Xol }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 50; buildTime = 16; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 2 }) + .addPart(EntityVitalityModel().apply { health = 125; defenseLayer = 40; defense = DefenseType.Overgrowth; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 7; range = 400; attacksPerSecond = 1.4f; targets = TargetType.All; mediumDamage = 6 + heavyDamage = 5 + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_Offering }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.ABILITY_Offering }), + + IdsEntity.UNIT_Xacal to EntityModel(IdsEntity.UNIT_Xacal, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Xacal" + descriptive = DescriptiveType.Skirmisher + description = """ + Ground unit. Attacks ground targets. Anti-Armor specialist. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 30; buildTime = 20; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 210; defenseLayer = 110; armor = ArmourType.Medium }) + .addPart(EntityMovementModel().apply { speed = 516f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 14; mediumDamage = 21; heavyDamage = 28; range = 600; secondsBetweenAttacks = 1.786f + targets = TargetType.Ground + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_EthericFibers }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_XacalDamage }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_XacalDefense }), + + IdsEntity.UNIT_Bloodbound to EntityModel(IdsEntity.UNIT_Bloodbound, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Bloodbound" + descriptive = DescriptiveType.Frontliner + description = """ + Ground unit. Attacks ground targets. Hard to kill. + """.trimIndent() + flavorText = """ + "With this act, I sever myself from families proud and pleasures fickle. My hands shall + clutch no babe nor trophy, for I am Laculathon's hungering blade." +
+ —Bloodbound initiation Rites + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Xol; replacedById = IdsEntity.VANGUARD_WhiteWoodReaper_Xol }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 80; buildTime = 24; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 4 }) + .addPart(EntityVitalityModel().apply { energy = 60; health = 150; defenseLayer = 75; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 500f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 30; mediumDamage = 40; lightDamage = 50; range = 80; attacksPerSecond = 1.4f + targets = TargetType.Ground + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_QuenchingScythes }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.ABILITY_BloodyRebound }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_CastingFromBlood }), + + IdsEntity.UNIT_BloodAnchor to EntityModel(IdsEntity.UNIT_BloodAnchor, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Blood Anchor" + descriptive = DescriptiveType.ZoneControl + description = """ + Ground unit. Can't attack. Deploys to spawn Cystic Crawlers and Rootway. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Atzlan; replacedById = IdsEntity.VANGUARD_Resinant_Atzlan }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 125; ether = 100; buildTime = 32; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 5 }) + .addPart(EntityVitalityModel().apply { health = 150; defenseLayer = 75; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 500f; movement = MovementType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.ABILITY_MobilizeAru }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.ABILITY_DeployBloodAnchor }), + + IdsEntity.UNIT_RedSeer to EntityModel(IdsEntity.UNIT_RedSeer, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Red Seer" + descriptive = DescriptiveType.DamageCaster + description = """ + Ground Spellcaster. Casts damage and utility spells. Ultimate Spellcaster. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala; replacedById = IdsEntity.VANGUARD_DreadSister_Mala }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 40; ether = 140; buildTime = 32; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { + energy = 100; health = 90; defenseLayer = 75; defense = DefenseType.Overgrowth + isEtheric = true; armor = ArmourType.Light + }) + .addPart(EntityMovementModel().apply { speed = 360f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 9; range = 700; attacksPerSecond = 1.253f; targets = TargetType.All }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_BloodPlague }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_DrainingEmbrace }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_MorphToGodphage }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_CastingFromBlood }), + + IdsEntity.UNIT_Underspine to EntityModel(IdsEntity.UNIT_Underspine, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Underspine" + descriptive = DescriptiveType.Support + description = "Ground unit. Can attack ground and air." + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityVanguardReplacedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala; replacedById = IdsEntity.VANGUARD_Incubator_Mala }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 70; ether = 50; buildTime = 20; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 210; defenseLayer = 60; armor = ArmourType.Medium }) + .addPart(EntityMovementModel().apply { speed = 400f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 2; range = 600; secondsBetweenAttacks = 1.253f; targets = TargetType.All + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_OssifyingSwarm }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_ObstructingSwarm }), + + IdsEntity.UNIT_Ichor to EntityModel(IdsEntity.UNIT_Ichor, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Ichor" + descriptive = DescriptiveType.Skirmisher + description = """ + Ground unit. Attacks ground targets. Fragile and agile. + """.trimIndent() + flavorText = """ + "Kissed an ichor:" Aru phrase for "dying by one's own stupidity." + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 1.5f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 0; buildTime = 24; producedBy = IdsEntity.BUILDING_AltarOfTheWorthy }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_MurderHollow + requirement = RequirementType.Research_Building + }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 100; defenseLayer = 40; armor = ArmourType.Medium }) + .addPart(EntityMovementModel().apply { speed = 424f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { + damage = 18; lightDamage = 38; mediumDamage = 28; range = 500; attacksPerSecond = 0.7f + targets = TargetType.Ground; secondsBetweenAttacks = 1.429f + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_PursuitLigaments }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_ExternalDigestion }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_PursuitLigaments }), + + IdsEntity.UNIT_Aarox to EntityModel(IdsEntity.UNIT_Aarox, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Aarox" + descriptive = DescriptiveType.AirKiller + description = """ + Air unit. Attacks air targets. Self-destructs to do area damage. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BoneCanopy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 20; ether = 20; buildTime = 24; producedBy = IdsEntity.BUILDING_BoneCanopy }) + .addPart(EntitySupplyModel().apply { takes = 2 }) + .addPart(EntityVitalityModel().apply { health = 55; defenseLayer = 10; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 490f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { + damage = 125; mediumDamage = 163; heavyDamage = 200; range = 20 +attacksPerSecond = 1f + targets = TargetType.Air + }) + .addPart(EntityIdAbilityModel().apply { id = IdsEntity.ABILITY_DiveBomb }), + + IdsEntity.UNIT_Thrum to EntityModel(IdsEntity.UNIT_Thrum, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Thrum" + descriptive = DescriptiveType.Skirmisher + description = """ + Air unit. Attacks ground/air targets. Fragile and agile.
+ Thrums get attack speed when a Thrum kills an enemy. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BoneCanopy + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 65; buildTime = 24; producedBy = IdsEntity.BUILDING_BoneCanopy }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 150; defenseLayer = 50; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 580f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { + damage = 11; lightDamage = 15; mediumDamage = 13; range = 350; secondsBetweenAttacks = 1.25f + targets = TargetType.All + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_BloodFrenzy }), + + IdsEntity.UNIT_WraithBow to EntityModel(IdsEntity.UNIT_WraithBow, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Wraith Bow" + descriptive = DescriptiveType.AirKiller + description = """ + Ground unit. Attacks ground/air targets. Anti-air specialist. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 2f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Production_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 80; ether = 40; buildTime = 24; producedBy = IdsEntity.BUILDING_AmberWomb }) + .addPart(EntitySupplyModel().apply { takes = 3 }) + .addPart(EntityVitalityModel().apply { health = 180; defenseLayer = 65; armor = ArmourType.Medium }) + .addPart(EntityMovementModel().apply { speed = 500f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 22; range = 700; attacksPerSecond = 1.4f; targets = TargetType.Air }) + .addPart(EntityWeaponModel().apply { damage = 9; range = 500; attacksPerSecond = 1.4f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_GuidingAmber }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_WraithBowRange }), + + IdsEntity.UNIT_Behemoth to EntityModel(IdsEntity.UNIT_Behemoth, EntityType.Army) + .addPart(EntityInfoModel().apply { + name = "Behemoth" + descriptive = DescriptiveType.Skirmisher + description = """ + Air unit. Attacks ground targets. Long-range attack. + """.trimIndent() + }) + .addPart(EntityTierModel().apply { tier = 3.5f }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BoneCanopy + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; holdSpace = true; hotkeyGroup = "Z" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 190; ether = 150; buildTime = 40; producedBy = IdsEntity.BUILDING_BoneCanopy }) + .addPart(EntitySupplyModel().apply { takes = 8 }) + .addPart(EntityVitalityModel().apply { health = 350; defenseLayer = 100; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 210f; movement = MovementType.Air }) + .addPart(EntityWeaponModel().apply { + damage = 21 + range = 600; attacksPerSecond = 0.588f; secondsBetweenAttacks = 1.7f + cooldown = 6f; charges = 1f + targets = TargetType.Ground + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_VitellineCysts }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_BehemothCapacity }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_QuitlStorage2 }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_FireQuitl }), + + IdsEntity.SUMMON_Quitl to EntityModel(IdsEntity.SUMMON_Quitl, EntityType.Army) + .addPart(EntityInfoModel().apply { name = "Quitl"; descriptive = DescriptiveType.Summon }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVitalityModel().apply { health = 65; armor = ArmourType.Light; lasts = 8 }) + .addPart(EntityMovementModel().apply { speed = 168f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 10; range = 300; attacksPerSecond = 1.124f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Temporary }) ) } - fun get(): Map { - if (_cache == null) { - _cache = getResearchData() + - getArmyData() + - getAbilityData() + - getMiscData() + - getImmortalData() + - getBuildingData() + - getPassiveData() + - getEssentialEntities() - } - return _cache!! + private fun getAbilityData(): Map { + return mapOf( + IdsEntity.ABILITY_RadiantWard to EntityModel(IdsEntity.ABILITY_RadiantWard, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Radiant Ward"; descriptive = DescriptiveType.Ability; description = """Target a location. A Hidden mine is laid there. + +Detonates when enemy ground units get close. Enemies hit +are revealed, slowed, and take additional damage.""" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "D"; holdSpace = true }) + .addPart(EntityProductionModel().apply { defensiveLayer = 45; cooldown = 40f }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_RadiantWard }) + .addPart(EntityVitalityModel().apply { health = 30; defenseLayer = 30; lasts = 60; armor = ArmourType.Light }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.BUILDING_HouseOfFadingSaints; requirement = RequirementType.Production_Building }), + + IdsEntity.PASSIVE_Maledictions to EntityModel(IdsEntity.PASSIVE_Maledictions, EntityType.Passive) + .addPart(EntityInfoModel().apply { name = "Maledictions"; descriptive = DescriptiveType.Passive; description = """Stun ground units? With Maledictions passive."""; notes = "Not implemented" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.ABILITY_Windstep to EntityModel(IdsEntity.ABILITY_Windstep, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Windstep"; descriptive = DescriptiveType.Ability; description = """Target a location. Teleport to location: costs Shields. + +Decrease cooldown and avoid Shield cost when teleporting to Hallowed Ground.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 20f }), + + IdsEntity.ABILITY_Intervention to EntityModel(IdsEntity.ABILITY_Intervention, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Intervention"; descriptive = DescriptiveType.Ability; description = "Target a location. A Saoshin leaps to the location and heals friendly units." }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 30f }), + + IdsEntity.ABILITY_OrdainedPassage to EntityModel(IdsEntity.ABILITY_OrdainedPassage, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Ordained Passage"; descriptive = DescriptiveType.Ability; description = """Target an area to send a damage reduction to circle to. + +The circle starts at the Ark Mother, travels to the target location, and stays there for a duration. +Friendly units get damage reduction while in the circle, including when it is movie. +Costs all the Ark Mother's Shields.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "D"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 3f; energy = 55 }), + + IdsEntity.ABILITY_DeployAbsolver to EntityModel(IdsEntity.ABILITY_DeployAbsolver, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Absolver"; descriptive = DescriptiveType.Ability; description = """Deploying the Absolver drastically increases its attack speed.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.ABILITY_DeployMagi to EntityModel(IdsEntity.ABILITY_DeployMagi, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Magi"; descriptive = DescriptiveType.Ability; description = """Deploys the Magi to project Hallowed Ground around it.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.ABILITY_DeploySentinel to EntityModel(IdsEntity.ABILITY_DeploySentinel, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Sentinel"; descriptive = DescriptiveType.Ability; description = """Deploy to absorb enemy projectiles. Costs Shields. + +Deployed units cannot move. Use Mobilize to move again.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "D"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.ABILITY_Smite to EntityModel(IdsEntity.ABILITY_Smite, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Smite"; descriptive = DescriptiveType.Ability; description = """Target an enemy unit. Deal damage after a delay. + +After being targeted, the enemy can move out of range to +cancel damage. Can store up to 2 charges.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 2f; energy = 30 }), + + IdsEntity.ABILITY_Awestrike to EntityModel(IdsEntity.ABILITY_Awestrike, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Awestrike"; descriptive = DescriptiveType.Ability; description = """Target and area. Damages enemy ground units. + +Units in the center of the area take more damage. +Units take damage over time after the initial burst of damage and continue to take damage +over time after leaving the area.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 8f; energy = 60 }), + + IdsEntity.ABILITY_TitheBlades to EntityModel(IdsEntity.ABILITY_TitheBlades, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Tithe Blades"; descriptive = DescriptiveType.Ability; description = """Activate to steal Shields from your nearby ground units, up to a cap. + +Also increases attack speed based on amount of Shields stolen.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "D"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { cooldown = 70f }), + + IdsEntity.ABILITY_MobilizeQrath to EntityModel(IdsEntity.ABILITY_MobilizeQrath, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Mobilize Q'Rath"; descriptive = DescriptiveType.Ability; description = "Mobilize all deployed Q'Rath units." }) + .addPart(EntityHotkeyModel().apply { hotkey = "TAB"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.ABILITY_MobilizeAru to EntityModel(IdsEntity.ABILITY_MobilizeAru, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Mobilize Aru"; descriptive = DescriptiveType.Ability; description = """Mobilize all deployed Aru units.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_Offering to EntityModel(IdsEntity.ABILITY_Offering, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Offering"; descriptive = DescriptiveType.Ability; description = "Sacrifices 10 life to give Masked Hunters +3 damage for 3 shots. And increased speed and attack speed." }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_Offering }) + .addPart(EntityProductionModel().apply { cooldown = 5f }), + + IdsEntity.ABILITY_DiveBomb to EntityModel(IdsEntity.ABILITY_DiveBomb, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Dive Bomb"; descriptive = DescriptiveType.Ability; description = "The aarox dives down into the ground, dealing damage in a smaller area. Non-hovering units in the area take additional damage over time." }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_DrainingEmbrace to EntityModel(IdsEntity.ABILITY_DrainingEmbrace, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Draining Embrace"; descriptive = DescriptiveType.Ability; description = "Units in the target area are rooted and lose energy." }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "D" }) + .addPart(EntityProductionModel().apply { energy = 30; cooldown = 5f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_BloodPlague to EntityModel(IdsEntity.ABILITY_BloodPlague, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Blood Plague"; descriptive = DescriptiveType.Ability; description = "Damage is based on maximum HP. Damage cannot kill a unit." }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "D" }) + .addPart(EntityProductionModel().apply { energy = 90; cooldown = 8f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_DeployResinant to EntityModel(IdsEntity.ABILITY_DeployResinant, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Resinant"; descriptive = DescriptiveType.Ability; description = "Get even more attack range if deployed on Rootway. Deploying a unit makes it unable to move. Use Mobilize to move again." }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_DeployBloodAnchor to EntityModel(IdsEntity.ABILITY_DeployBloodAnchor, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Blood Anchor"; descriptive = DescriptiveType.Ability; description = """Activate to deploy and begin spawning Cystic Crawlers.
+
+Generates Rootway. Cystic Crawlers are temporary units that cannot survive off Rootway and self-destruct to deal +area damage. Deployed units cannot move. Use Mobilize to move again.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_BloodyRebound to EntityModel(IdsEntity.ABILITY_BloodyRebound, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Bloody Rebond"; descriptive = DescriptiveType.Ability; description = """Active to prevent death for a duration. +Instead of dying, the Bloodbound will teleport to the place it was when the ability was activated. +It also takes less damage while Blood Rebound is active.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "D" }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { energy = 50; cooldown = 40f }), + + IdsEntity.ABILITY_RootVice to EntityModel(IdsEntity.ABILITY_RootVice, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Root Vice"; descriptive = DescriptiveType.Ability; description = "Roots all units for several seconds, then leaves them slowed for several seconds after." }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "D" }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { energy = 50; cooldown = 10f }), + + IdsEntity.ABILITY_BirthingStorm to EntityModel(IdsEntity.ABILITY_BirthingStorm, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Birthing Storm"; descriptive = DescriptiveType.Ability; description = """Target an area to deal damage and Seed enemies."""; notes = """Deals 10 damage + 5% of max life of the target immediately upon affecting the enemy unit.
+It deals 15 damage + 15% after 8 seconds. If the unit dies during those 8 seconds (including the final burst), +spawns 1 quitl every 2 supply of the dead unit, rounded up. Stacking only refreshes duration of debuff.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "D" }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { energy = 95; cooldown = 8f }), + + IdsEntity.ABILITY_DeployDreadSister to EntityModel(IdsEntity.ABILITY_DeployDreadSister, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deploy Dread Sister"; descriptive = DescriptiveType.Dislodger; description = """Deploy to get more damage and more range.
+
+Can only attack ground. Each shot cost Energy. Use Mobilize to move again.""" }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala; replaceId = IdsEntity.UNIT_Godphage }), + + IdsEntity.ABILITY_MorphToGodphage to EntityModel(IdsEntity.ABILITY_MorphToGodphage, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Morph To Godphage"; descriptive = DescriptiveType.Dislodger; description = "Active to morph a Red Seer into a Godphage" }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 150; energy = 100; buildTime = 25 }) + .addPart(EntitySupplyModel().apply { takes = 8 }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UNIT_RedSeer; requirement = RequirementType.Morph }) + .addPart(EntityVitalityModel().apply { health = 225; defenseLayer = 375; armor = ArmourType.Heavy }) + .addPart(EntityMovementModel().apply { speed = 350f; movement = MovementType.Ground }) + .addPart(EntityWeaponModel().apply { damage = 155; range = 1100; secondsBetweenAttacks = 7f; targets = TargetType.Ground }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HiddenX }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_GodphageDamage }), + + IdsEntity.ABILITY_DeepTunnel to EntityModel(IdsEntity.ABILITY_DeepTunnel, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Deep Tunnel" }) + .addPart(EntityHotkeyModel().apply { hotkey = "TAB"; holdSpace = true; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_ObstructingSwarm to EntityModel(IdsEntity.ABILITY_ObstructingSwarm, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Obstructing Swarm" }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.ABILITY_Hematoma to EntityModel(IdsEntity.ABILITY_Hematoma, EntityType.Ability) + .addPart(EntityInfoModel().apply { name = "Hematoma" }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "D" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + ) } + + private fun getBuildingData(): Map { + return mapOf( + // Building + // Q'Rath + IdsEntity.BUILDING_Acropolis to EntityModel(IdsEntity.BUILDING_Acropolis, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Acropolis" + descriptive = DescriptiveType.Stronghold + description = """ + Collect Alloy and Ether.
+ If all your Strongholds are destroyed, you lose the game. + """.trimIndent() + flavorText = """ + "Bereft of Ancient Kin, from My burning heart new mothers shall be made and woven onto + the grateful earth. And within their walls, the righteous shall gather in great number."
+ —Holy Aros, "Utterances 14.7" + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 450; buildTime = 90; requiresWorker = true; consumesWorker = true + }) + .addPart(EntityVitalityModel().apply { + health = 2300; defenseLayer = 1200; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1f; requiresWorker = true; resource = ResourceType.Alloy; slots = 2f + totalAmount = 3600 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + IdsEntity.BUPGRADE_MiningLevel2_QRath to EntityModel(IdsEntity.BUPGRADE_MiningLevel2_QRath, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "Mining Level 2" + descriptive = DescriptiveType.Upgrade + description = "Upgrades the nearest resource cluster to allow more workers to mine from it." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "CONTROL" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Acropolis + requirement = RequirementType.Morph + }) + .addPart(EntityProductionModel().apply { alloy = 400; buildTime = 20; requiresWorker = false }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1f; requiresWorker = true; resource = ResourceType.Alloy; slots = 4f + totalAmount = 3600 + }), + + IdsEntity.CONVERSION_EtherSruge_Aru to EntityModel(IdsEntity.CONVERSION_EtherSruge_Aru, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "Ether Surge" + descriptive = DescriptiveType.Upgrade + description = "Gain 100 Ether over 50 seconds" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "CONTROL" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.STARTING_TownHall_Aru + requirement = RequirementType.Morph + }) + .addPart(EntityProductionModel().apply { alloy = 50; cooldown = 50f; requiresWorker = false }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 2f; requiresWorker = false; resource = ResourceType.Ether; slots = 1f + totalAmount = 100 + }), + + IdsEntity.CONVERSION_EtherSruge_QRath to EntityModel(IdsEntity.CONVERSION_EtherSruge_QRath, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "Ether Surge" + descriptive = DescriptiveType.Upgrade + description = "Gain 100 Ether over 50 seconds" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "CONTROL" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.STARTING_TownHall_QRath + requirement = RequirementType.Morph + }) + .addPart(EntityProductionModel().apply { alloy = 50; cooldown = 50f; requiresWorker = false }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 2f; requiresWorker = false; resource = ResourceType.Ether; slots = 1f + totalAmount = 100 + }), + + IdsEntity.BUILDING_ApostleOfBinding to EntityModel(IdsEntity.BUILDING_ApostleOfBinding, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Apostle of Binding" + descriptive = DescriptiveType.Economy + description = """ + Automatically harvest Ether. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "TAB"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 175; buildTime = 30; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 300; defenseLayer = 200; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1.5625f; requiresWorker = false; resource = ResourceType.Ether; slots = 1f + totalAmount = 1200 + }), + + IdsEntity.BUILDING_LegionHall to EntityModel(IdsEntity.BUILDING_LegionHall, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Legion Hall" + descriptive = DescriptiveType.Training + description = """ + Trains Sipari, Zephyrs, Magi, and their Vanguard Replacements. Increases Population Capacity. + """.trimIndent() + flavorText = """ + "May the angels' army grow in size. May its many arms grow in strength. And may its soul grow in faith."
+ —Steel Banner-Lord Sian + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityProductionModel().apply { alloy = 250; buildTime = 38; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 600; defenseLayer = 600; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + IdsEntity.DEFENSE_FireSinger to EntityModel(IdsEntity.DEFENSE_FireSinger, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Fire Singer" + descriptive = DescriptiveType.Defense + description = """ + Attacks ground/air targets. Does damage over time. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 75; buildTime = 30; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_KeeperOfTheHardenedFlames + requirement = RequirementType.Research_Building + }) + .addPart(EntityWeaponModel().apply { + damage = 34; range = 700; secondsBetweenAttacks = 1.8f; targets = TargetType.All + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedWeapons }) + .addPart(EntityVitalityModel().apply { + health = 350; defenseLayer = 200; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_PsalmOfFire }), + + IdsEntity.BUILDING_KeeperOfTheHardenedFlames to EntityModel(IdsEntity.BUILDING_KeeperOfTheHardenedFlames, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Keeper Of the Hardened Flames" + descriptive = DescriptiveType.Technology + description = """ + Unlocks Attack and Defense Upgrades for all units.
+ Unlocks Fire Singer building and research. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "TAB"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 100; buildTime = 30; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 625; defenseLayer = 625; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_Reliquary to EntityModel(IdsEntity.BUILDING_Reliquary, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Reliquary" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Magi and their Vanguard replacements.
+ Unlocks research for Sipari and their Vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 100; ether = 25; buildTime = 30; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 700; defenseLayer = 700; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_MonasteryOfIzur to EntityModel(IdsEntity.BUILDING_MonasteryOfIzur, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Monastery of Izur" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Zephyrs and their Vanguard replacements.
+ Unlocks research for Zephyrs and their Vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { alloy = 100; ether = 50; buildTime = 30; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 700; defenseLayer = 700; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_SoulFoundry to EntityModel(IdsEntity.BUILDING_SoulFoundry, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Soul Foundry" + descriptive = DescriptiveType.Training + description = """ + Trains Hallowers, Castigators, Absolvers, and their Vanguard replacements. Increases Population Capacity. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 250; ether = 100; buildTime = 42; requiresWorker = true + }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_LegionHall + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 900; defenseLayer = 900; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + IdsEntity.BUILDING_HouseOfFadingSaints to EntityModel(IdsEntity.BUILDING_HouseOfFadingSaints, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "House of the Fading Saints" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Hallowers and their Vanguard replacements. + """.trimIndent() + flavorText = """ + "And soul pases unto the horizon, joining to the Holiest of Holies and the Host of Heaven."
+ — Ceremony of the Passing + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 45; requiresWorker = true + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 500; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_Angelarium to EntityModel(IdsEntity.BUILDING_Angelarium, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Angelarium" + descriptive = DescriptiveType.Training + description = """ + Trains Sentinels, Wardens, Thrones, Shar'U and their Vanguard replacements. Increases Population Capacity. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 250; ether = 150; buildTime = 42; requiresWorker = true + }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_SoulFoundry + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 950; defenseLayer = 950; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + IdsEntity.BUILDING_EyeOfAros to EntityModel(IdsEntity.BUILDING_EyeOfAros, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Eye of Aros" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Shar'U.
+ Unlocks research for Sipari, Shar'U, and their Vanguard replacements. + """.trimIndent() + flavorText = """ + "Where My gaze shall fall, blessed son and sun of Mine, you shall go forth + and claim in Our Holy Cause. For you are My Lance and My Rook, and you shall never fail me."
+ —Holy Aros to Orzum + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 200; ether = 250; buildTime = 60; requiresWorker = true + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 550; defenseLayer = 550; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_BearerOfTheCrown to EntityModel(IdsEntity.BUILDING_BearerOfTheCrown, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Bearer of the Crown" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Thrones.
+ Unlocks research for: Thrones, Wardens, and their vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 250; ether = 250; buildTime = 70; requiresWorker = true + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Angelarium + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 625; defenseLayer = 625; armor = ArmourType.Heavy; isStructure = true + }), + + // Building + // Aru + IdsEntity.BUILDING_GroveHeart to EntityModel(IdsEntity.BUILDING_GroveHeart, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Grove Heart" + descriptive = DescriptiveType.Stronghold + description = """ + Collect Alloy and Ether.
+ If all your Strongholds are destroyed, you lose the game. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 500; buildTime = 90; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 2000; defenseLayer = 400; defense = DefenseType.Overgrowth; armor = ArmourType.Heavy + isStructure = true + }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1f; requiresWorker = true; resource = ResourceType.Alloy; slots = 2f + totalAmount = 3600 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + IdsEntity.BUPGRADE_GodHeart to EntityModel(IdsEntity.BUPGRADE_GodHeart, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "God Heart" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; hotkeyGroup = "CONTROL" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVitalityModel().apply { + health = 3100; defenseLayer = 900; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.STARTING_TownHall_Aru + requirement = RequirementType.Morph + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { alloy = 200; ether = 100; buildTime = 60; requiresWorker = false }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + IdsEntity.BUPGRADE_MiningLevel2_Aru to EntityModel(IdsEntity.BUPGRADE_MiningLevel2_Aru, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "Mining Level 2" + descriptive = DescriptiveType.Upgrade + description = "Upgrades the nearest resource cluster to allow more workers to mine from it." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "CONTROL" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_GroveHeart + requirement = RequirementType.Morph + }) + .addPart(EntityProductionModel().apply { alloy = 400; buildTime = 20; requiresWorker = false }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1f; requiresWorker = true; resource = ResourceType.Alloy; slots = 2f + totalAmount = 3600 + }), + + IdsEntity.BUILDING_EtherMaw to EntityModel(IdsEntity.BUILDING_EtherMaw, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Ether Maw" + descriptive = DescriptiveType.Economy + description = "Automatically harvest Ether." + flavorText = """ + "The Matriarchs tell of where the ether goes. ARound Lacuathon's trunk swirls a great aurora of + this world's lifeblood, flowing upwards into the half-eaten sky."
+ —Hunter Ipotzil + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "TAB"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 225; buildTime = 30; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 400; defenseLayer = 225; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1.5625f; requiresWorker = false; resource = ResourceType.Ether + slots = 1f; totalAmount = 1200 + }), + + IdsEntity.BUILDING_AltarOfTheWorthy to EntityModel(IdsEntity.BUILDING_AltarOfTheWorthy, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Altar of the Worthy" + descriptive = DescriptiveType.Training + description = """ + Trains Masked Hunters, Xacal, Underspins, Ichors, and their Vanguard replacements. Increases Population Capacity. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityProductionModel().apply { alloy = 300; buildTime = 38; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 1000; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + IdsEntity.BUILDING_Neurocyte to EntityModel(IdsEntity.BUILDING_Neurocyte, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Neurocyte" + descriptive = DescriptiveType.Technology + description = """ + Unlocks training of Xacal, Underspines, Red Seers, Brood Anchors, Aarox, Behemoths, and their + Vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 100; ether = 75; buildTime = 30; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 800; defenseLayer = 200; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_RootCradle to EntityModel(IdsEntity.BUILDING_RootCradle, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Root Cradle" + descriptive = DescriptiveType.Technology + description = "Unlocks Attack and Defense Upgrades for all units." + flavorText = """ + Grinding and deep, Laculathon's Rootsong speaks of a time fast apporaching where the Worthy + shall rise and pierce the heart of the Wretched Sun. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Tab"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 0; buildTime = 30; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 900; defenseLayer = 300; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.DEFENSE_Aerovore to EntityModel(IdsEntity.DEFENSE_Aerovore, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Aerovore" + descriptive = DescriptiveType.Defense + description = "Attacks air targets." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 75; buildTime = 18; requiresWorker = true }) + .addPart(EntityVitalityModel().apply { + health = 300; defenseLayer = 150; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityWeaponModel().apply { + damage = 65; range = 700; secondsBetweenAttacks = 1.35f; targets = TargetType.Air + }), + + IdsEntity.BUPGRADE_Omnivore to EntityModel(IdsEntity.BUPGRADE_Omnivore, EntityType.Building_Upgrade) + .addPart(EntityInfoModel().apply { + name = "Omnivore" + descriptive = DescriptiveType.Upgrade + description = "Attacks ground/air targets." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "SHIFT" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.DEFENSE_Aerovore + requirement = RequirementType.Morph + }) + .addPart(EntityProductionModel().apply { alloy = 75; buildTime = 18; requiresWorker = false }) + .addPart(EntityVitalityModel().apply { + health = 400; defenseLayer = 150; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityWeaponModel().apply { + damage = 65; range = 700; secondsBetweenAttacks = 1.35f; targets = TargetType.Air + }) + .addPart(EntityWeaponModel().apply { + damage = 52; mediumDamage = 61; heavyDamage = 70; range = 700; secondsBetweenAttacks = 1.35f + targets = TargetType.Ground + }), + + IdsEntity.BUILDING_AmberWomb to EntityModel(IdsEntity.BUILDING_AmberWomb, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Amber Womb" + descriptive = DescriptiveType.Training + description = """ + Trains Wraith Bows, Brood Anchors, and their Vanguard replacements. Increases Population Capacity. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 300; ether = 100; buildTime = 42; requiresWorker = true }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUPGRADE_GodHeart + requirement = RequirementType.Research_Building + }) + .addPart(EntityVitalityModel().apply { + health = 1000; defenseLayer = 600; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + IdsEntity.BUILDING_BoneCanopy to EntityModel(IdsEntity.BUILDING_BoneCanopy, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Bone Canopy" + descriptive = DescriptiveType.Training + description = """ + Trains Aarox, Thrums, Behemoths, and their Vanguard replacements. Increases Population Capacity. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 300; ether = 150; buildTime = 42; requiresWorker = true }) + .addPart(EntitySupplyModel().apply { grants = 16 }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUPGRADE_GodHeart + requirement = RequirementType.Research_Building + }) + .addPart(EntityVitalityModel().apply { + health = 1100; defenseLayer = 650; armor = ArmourType.Heavy; isStructure = true + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + IdsEntity.BUILDING_MurderHollow to EntityModel(IdsEntity.BUILDING_MurderHollow, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Murder Hollow" + descriptive = DescriptiveType.Technology + description = """ + Unlocks Ichor training and research. + """.trimIndent() + flavorText = """ + "Foolish Otapeke thought himself the master of Her children. Now he eases the acid + in their bellies. Stay for from the Hollow when they howl like that."
+ —Huntmaster Etatzli to his initiates + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 75; ether = 50; buildTime = 45; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 1000; defenseLayer = 400; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_RedVale to EntityModel(IdsEntity.BUILDING_RedVale, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Red Vale" + descriptive = DescriptiveType.Technology + description = """ + Unlock training of Red Seer and their Vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 150; ether = 175; buildTime = 60; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Production_Building + }) + .addPart(EntityVitalityModel().apply { + health = 1000; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true + }), + + IdsEntity.BUILDING_DeepNest to EntityModel(IdsEntity.BUILDING_DeepNest, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Deep Nest" + descriptive = DescriptiveType.Technology + description = """ + Unlock training of Behemoth and their Vanguard replacements. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "C" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { alloy = 225; ether = 175; buildTime = 70; requiresWorker = true }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BoneCanopy + requirement = RequirementType.Research_Building + }) + .addPart(EntityVitalityModel().apply { + health = 1000; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true + }) + ) + } + + private fun getImmortalData(): Map { + return mapOf( + // Immortals + // Aru + IdsEntity.IMMORTAL_Atzlan to EntityModel(IdsEntity.IMMORTAL_Atzlan, EntityType.Immortal) + .addPart(EntityInfoModel().apply { name = "Atzlan" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityHarvestModel().apply { + resource = ResourceType.Pyre; harvestedPerInterval = 1f; harvestDelay = 3f + requiresWorker = false; slots = 1f; totalAmount = -1 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.IPASSIVE_GreenThumb }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonGroveGuardian }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_ProphetOfTheRoots }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_WallOfRoots }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonDeepWyrm }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonRootBud }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_RootShepard_Atzlan }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_Resinant_Atzlan }), + IdsEntity.IMMORTAL_Mala to EntityModel(IdsEntity.IMMORTAL_Mala, EntityType.Immortal) + .addPart(EntityInfoModel().apply { name = "Mala" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityHarvestModel().apply { + resource = ResourceType.Pyre; harvestedPerInterval = 1f; harvestDelay = 3f + requiresWorker = false; slots = 1f; totalAmount = -1 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.IPASSIVE_MothersHunger }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonGroveGuardian }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_RedHarvest }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_ProphetsFavor }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_RainOfBlood }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_ConstructBloodWell }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_Incubator_Mala }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_DreadSister_Mala }), + IdsEntity.IMMORTAL_Xol to EntityModel(IdsEntity.IMMORTAL_Xol, EntityType.Immortal) + .addPart(EntityInfoModel().apply { name = "Xol" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityHarvestModel().apply { + resource = ResourceType.Pyre; harvestedPerInterval = 1f; harvestDelay = 3f + requiresWorker = false; slots = 1f; totalAmount = -1 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.IPASSIVE_StalkersSense }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonGroveGuardian }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_ProphetOfTheHunt }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_HuntingGrounds }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_TheGreatHunt }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_ConstructBloodWell }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_BoneStalker_Xol }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_WhiteWoodReaper_Xol }), + // Q'Rath + IdsEntity.IMMORTAL_Ajari to EntityModel(IdsEntity.IMMORTAL_Ajari, EntityType.Immortal) + .addPart(EntityInfoModel().apply { name = "Ajari" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityHarvestModel().apply { + resource = ResourceType.Pyre; harvestedPerInterval = 1f; harvestDelay = 3f + requiresWorker = false; slots = 1f; totalAmount = -1 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.IPASSIVE_MendingGrace }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonCitadel }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_HeavensAegis }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_DeliverFromEvil }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_Salvation }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_Saoshin_Ajari }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_ArkMother_Ajari }), + IdsEntity.IMMORTAL_Orzum to EntityModel(IdsEntity.IMMORTAL_Orzum, EntityType.Immortal) + .addPart(EntityInfoModel().apply { name = "Orzum" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityHarvestModel().apply { + resource = ResourceType.Pyre; harvestedPerInterval = 1f; harvestDelay = 3f + requiresWorker = false; slots = 1f; totalAmount = -1 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.IPASSIVE_OrdainedConquest }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_RookOfIra }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_SummonCitadel }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_EmpireUnbroken }) + .addPart(EntityIdPyreSpellModel().apply { id = IdsEntity.ISPELL_PillarOfHeaven }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_Zentari_Orzum }) + .addPart(EntityIdVanguardModel().apply { id = IdsEntity.VANGUARD_Sceptre_Orzum }), + + // Immortal Passives + IdsEntity.IPASSIVE_MendingGrace to EntityModel(IdsEntity.IPASSIVE_MendingGrace, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Mending Grace" + description = "Friendly units heal on Hallowed Ground. Units get an initial burst of healing after a delay, then heal over time." + }), + IdsEntity.IPASSIVE_OrdainedConquest to EntityModel(IdsEntity.IPASSIVE_OrdainedConquest, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Ordained Conquest" + description = """ + Citadels generate Pyre passively. Execute towers below 20% HP. + """.trimIndent() + }), + IdsEntity.IPASSIVE_MothersHunger to EntityModel(IdsEntity.IPASSIVE_MothersHunger, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Mother's Hunger" + description = """ + Units that die near Blood Wells, Grove Guardians, Incubators, Dread Sisters, Mala's Specter, + and units that dies during Rain of Blood give Sacral Blood. + """.trimIndent() + }), + IdsEntity.IPASSIVE_StalkersSense to EntityModel(IdsEntity.IPASSIVE_StalkersSense, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Stalker's Sense" + description = "Increases the vision range of your units by 1." + }), + IdsEntity.IPASSIVE_GreenThumb to EntityModel(IdsEntity.IPASSIVE_GreenThumb, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Green Thumb" + description = "Friendly units near Atzlan heal over time." + }), + + // Pyre Spells + // Q'Rath + IdsEntity.ISPELL_SummonCitadel to EntityModel(IdsEntity.ISPELL_SummonCitadel, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Summon Citadel" + description = """ + Target a Phyric Foundation. Summon a Citadel.
+ Can attack ground and air. Heals friendly units. Does not require a worker. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { pyre = 50; buildTime = 70 }) + .addPart(EntityVitalityModel().apply { health = 1000; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityWeaponModel().apply { + damage = 20; range = 800; attacksPerSecond = 1.124f; targets = TargetType.All + mediumDamage = 25; heavyDamage = 30 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Respite }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + // Orzum + IdsEntity.ISPELL_RookOfIra to EntityModel(IdsEntity.ISPELL_RookOfIra, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Rook of Ira" + description = "Target a location to summon a Rook of Ira." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Orzum }) + .addPart(EntityProductionModel().apply { pyre = 125; cooldown = 90f; buildTime = 3 }), + IdsEntity.ISPELL_EmpireUnbroken to EntityModel(IdsEntity.ISPELL_EmpireUnbroken, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Empire Unbroken" + description = """ + Targe an area. Buildings get damage reduction then heal.
+ Temporary. Healing happens at the end of the spell.
+ Temporarily turns Citadels into Rooks of Ira. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Orzum }) + .addPart(EntityProductionModel().apply { pyre = 50; cooldown = 120f }), + IdsEntity.ISPELL_PillarOfHeaven to EntityModel(IdsEntity.ISPELL_PillarOfHeaven, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Pillar of the Heavens" + description = """ + Target an area to deal massive area damage.
+ Creates temporary Hallowed Ground that improves friendly unit attack speed. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Orzum }) + .addPart(EntityProductionModel().apply { pyre = 150; cooldown = 120f }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Zeal }), + // Ajari + IdsEntity.ISPELL_HeavensAegis to EntityModel(IdsEntity.ISPELL_HeavensAegis, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Heaven's Aegis" + description = """ + Target a friendly unit. It gets movement speed and Shields.
+ Periodically saves charges, up to a cap. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Ajari }) + .addPart(EntityProductionModel().apply { pyre = 25; cooldown = 0.5f }), + IdsEntity.ISPELL_DeliverFromEvil to EntityModel(IdsEntity.ISPELL_DeliverFromEvil, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Deliver from Evil" + description = """ + Target an area. Friendly units teleport to your nearest Acropolis.
+ Units get Shields for a few seconds before teleporting. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Ajari }) + .addPart(EntityProductionModel().apply { pyre = 50; cooldown = 60f }), + IdsEntity.ISPELL_Salvation to EntityModel(IdsEntity.ISPELL_Salvation, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Salvation" + description = """ + Target a location. Summon Ajari's Specter to prevent death.
+ Saved units are brought back to Ajari's location at the end of the duration. Destroying Ajari's + Urn ends the effect and prevents Saved units from coming back. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Ajari }) + .addPart(EntityProductionModel().apply { pyre = 175; cooldown = 45f }), + // Aru + IdsEntity.ISPELL_SummonGroveGuardian to EntityModel(IdsEntity.ISPELL_SummonGroveGuardian, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Summon Grove Guardian" + description = "Creates a powerful defensive structure on a Tower Foundation." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 50; buildTime = 70 }) + .addPart(EntityVitalityModel().apply { health = 1850; defenseLayer = 450; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityWeaponModel().apply { damage = 19; range = 800; attacksPerSecond = 1.887f; targets = TargetType.All }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Respite }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + IdsEntity.ISPELL_ConstructBloodWell to EntityModel(IdsEntity.ISPELL_ConstructBloodWell, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Construct Blood Well" + description = "Creates a rootway generating structure that heals nearby allied units, and transfers it's blood to nearby allied units." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 25; cooldown = 21f; buildTime = 3 }) + .addPart(EntityVitalityModel().apply { health = 400; energy = 100; defenseLayer = 50; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_RestoreLifeblood }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Transfusion }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + // Atzlan + IdsEntity.ISPELL_ProphetOfTheRoots to EntityModel(IdsEntity.ISPELL_ProphetOfTheRoots, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Prophet Of The Roots" + description = "Moves Atzlan to target Root Bud. Blesses the Root Bud to give units nearby more overgrowth Atzlan heals nearby units." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 25; cooldown = 3f }) + .addPart(EntityVitalityModel().apply { health = 25; defenseLayer = 50; armor = ArmourType.Heavy; isStructure = true }), + IdsEntity.ISPELL_WallOfRoots to EntityModel(IdsEntity.ISPELL_WallOfRoots, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Wall of Roots" + description = """ + Spawn a Wall of Roots that blocks ground pathing.
+ Click and drag to "draw" the wall. Wall takes damage over time when it's off rootway. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 50; cooldown = 10f }) + .addPart(EntityVitalityModel().apply { health = 25; defenseLayer = 50; armor = ArmourType.Heavy; isStructure = true }), + IdsEntity.ISPELL_SummonDeepWyrm to EntityModel(IdsEntity.ISPELL_SummonDeepWyrm, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Summon Deep Wyrm" + description = """ + The Deep Wyrm roams in the area and attacks any ground enemy unit entering the rootway.
+ Spawns rootway on summon. The Deep Wyrm is invulnerable and uncontrollable. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 150; cooldown = 55f }) + .addPart(EntityVitalityModel().apply { health = 25; defenseLayer = 50; armor = ArmourType.Heavy; isStructure = true }), + IdsEntity.ISPELL_SummonRootBud to EntityModel(IdsEntity.ISPELL_SummonRootBud, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Summon Root Bud" + description = """ + Generates Rootway
+ Must be placed on Rootway and in range of another Root Bud or Stronghold + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { cooldown = 25f }) + .addPart(EntityVitalityModel().apply { health = 25; defenseLayer = 50; armor = ArmourType.Heavy; isStructure = true }), + // Mala + IdsEntity.ISPELL_RedHarvest to EntityModel(IdsEntity.ISPELL_RedHarvest, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Red Harvest" + description = """ + Target a location to summon Mala's Specter.
+ Nearby units make Quitl and give Sacral Blood on-death. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "V" }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { pyre = 75; cooldown = 90f }), + IdsEntity.ISPELL_ProphetsFavor to EntityModel(IdsEntity.ISPELL_ProphetsFavor, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Prophet's Favor" + description = """ + Target an area. Permanently empower units.
+ Empowered units get more HP and damage. Does not stack.
+ Costs Sacral Blood. Get Sacral Blood from Mala's other Powers and Vanguard. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "V" }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { cooldown = 5f }), + IdsEntity.ISPELL_RainOfBlood to EntityModel(IdsEntity.ISPELL_RainOfBlood, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "Rain of Blood" + description = "Rains blood from the sky for 30 seconds. Massively increases global life regeneration for allied troops. Allies anywhere also have significantly increased blood regeneration." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Mala }) + .addPart(EntityProductionModel().apply { pyre = 150; cooldown = 30f }), + // Xol + IdsEntity.ISPELL_ProphetOfTheHunt to EntityModel(IdsEntity.ISPELL_ProphetOfTheHunt, ImmortalSpellType.Combat) + .addPart(EntityInfoModel().apply { + name = "Prophet Of The Hunt" + description = """ + Target a location to summon Xol. + + Get XP by assiting in kills. Level up to become more powerful. + """.trimIndent() + notes = """ + - Summons Xol (stealthed) after a 1 second delay.
- Xol has 225 Life, 75 Shield, 410 Move speed, 70 Radius.
- Xol has a weapon: 28 Damage, 1s Cooldown, 7 Range, Ground and Air.
- Every 4 attacks, the next attack will deal 22 bonus damage.
+ """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Xol }) + .addPart(EntityProductionModel().apply { cooldown = 50f; pyre = 50 }), + IdsEntity.ISPELL_HuntingGrounds to EntityModel(IdsEntity.ISPELL_HuntingGrounds, ImmortalSpellType.Combat) + .addPart(EntityInfoModel().apply { + name = "Hunting Grounds" + description = """ + Target an area. Friendly units there become Hidden. + + After becoming Hidden, friendly units get more movement speed, attack speed, and damage on their first attack. + Hunting Grounds disappears 5 seconds after a unit inside attacks. + """.trimIndent() + notes = """ + - After a 10 second delay, creates a large ambush area which lasts until after an ambush is sprung.
- Units in the Hunting Ground become stealth.
- Stealth units will deal double damage on the first attack and spring the ambush.
- The Hunting Ground will disappear 5 seconds after an ambush is sprung.
- Units can only get the bonus once. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Xol }) + .addPart(EntityProductionModel().apply { cooldown = 10f; pyre = 25 }), + IdsEntity.ISPELL_TheGreatHunt to EntityModel(IdsEntity.ISPELL_TheGreatHunt, EntityType.Pyre_Spell) + .addPart(EntityInfoModel().apply { + name = "The Great Hunt" + description = """ + Activate to reduce enemy vision range and give friendly units movement speed. + Affects all units. Friendly units get attack speed on-kill + """.trimIndent() + notes = """ + - Reduces enemy vision to 6 range (used to be 3).
- After a 3 second delay, the hunt begins. The hunt lasts 20 seconds.
- Summons Xol (moveable) to lead the hunt.
- Units gain a 70% decaying move speed bonus for 6 seconds.
- While on the hunt killing an enemy will cause a frenzy giving the killer double attack speed for 7 seconds
- Adds duration timer to Xol + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "V" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVanguardAddedModel().apply { immortalId = IdsEntity.IMMORTAL_Xol }) + .addPart(EntityProductionModel().apply { pyre = 175; cooldown = 50f }) + ) + } + + private fun getMiscData(): Map { + return mapOf( + // Pyre Events + IdsEntity.PYREEVENT_CampTaken to EntityModel(IdsEntity.PYREEVENT_CampTaken, EntityType.Pyre_Event) + .addPart(EntityInfoModel().apply { name = "Pyre Camp"; description = "Provides 25 when taken." }) + .addPart(EntityPyreRewardModel().apply { baseReward = 25 }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "2" }), + IdsEntity.PYREEVENT_MinerTaken to EntityModel(IdsEntity.PYREEVENT_MinerTaken, EntityType.Pyre_Event) + .addPart(EntityInfoModel().apply { name = "Pyre Camp"; description = "Provides 90 when taken." }) + .addPart(EntityPyreRewardModel().apply { baseReward = 0; overTimeRewardDuration = 90; overTimeReward = 1f }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "2" }), + IdsEntity.PYREEVENT_TowerKilled to EntityModel(IdsEntity.PYREEVENT_TowerKilled, EntityType.Pyre_Event) + .addPart(EntityInfoModel().apply { name = "Tower Taken"; description = "Provides 10 when destroyed." }) + .addPart(EntityPyreRewardModel().apply { baseReward = 10 }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "2" }), + + // TEAPOTS + IdsEntity.TEAPOT_Teapot to EntityModel(IdsEntity.TEAPOT_Teapot, EntityType.Teapot) + .addPart(EntityInfoModel().apply { + name = "Teapot"; description = "Basic scout. Every faction has this" + notes = """Very powerful! So Fast""".trimIndent() + }) + .addPart(EntityVitalityModel().apply { health = 120; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 400f }), + IdsEntity.TEAPOT_FlyingTeapot to EntityModel(IdsEntity.TEAPOT_FlyingTeapot, EntityType.Teapot) + .addPart(EntityInfoModel().apply { + name = "Detector" + description = "Has 1100 vision, and can see hidden units within 1000 range." + notes = """Doesn't take up a scout slot.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.TEAPOT_Teapot }) + .addPart(EntitySupplyModel().apply { takes = 1 }) + .addPart(EntityProductionModel().apply { alloy = 100; ether = 50 }) + .addPart(EntityVitalityModel().apply { health = 120; defenseLayer = 80; armor = ArmourType.Light }) + .addPart(EntityMovementModel().apply { speed = 280f; movement = MovementType.Air }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Detection }), + + // Families + IdsEntity.FAMILY_Rae to EntityModel(IdsEntity.FAMILY_Rae, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Rae" }), + IdsEntity.FAMILY_Sylv to EntityModel(IdsEntity.FAMILY_Sylv, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Sylv" }), + IdsEntity.FAMILY_Angelic to EntityModel(IdsEntity.FAMILY_Angelic, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Angelic" }), + IdsEntity.FAMILY_Human to EntityModel(IdsEntity.FAMILY_Human, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Human" }), + IdsEntity.FAMILY_Coalition to EntityModel(IdsEntity.FAMILY_Coalition, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Coalition?" }), + IdsEntity.FAMILY_Demonic to EntityModel(IdsEntity.FAMILY_Demonic, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Demonic?" }), + IdsEntity.FAMILY_NazRa to EntityModel(IdsEntity.FAMILY_NazRa, EntityType.Family, true) + .addPart(EntityInfoModel().apply { name = "Naz'Ra" }), + + // Factions - Sylv + IdsEntity.FACTION_Aru to EntityModel(IdsEntity.FACTION_Aru, EntityType.Faction) + .addPart(EntityInfoModel().apply { name = "Aru" }) + .addPart(EntityPassiveModel().apply { + name = "Overgrowth" + description = "Your units have an extra layer of health a regens rapidly when a unit hasn't been damaged recently. This regen is doubled on rootway." + }) + .addPart(EntityPassiveModel().apply { + name = "Blood" + description = "Your casters passively get blood for spells. This blood regen rate is increased on rootway. Your casters can also spend their own life as blood. (Spending health as blood is currenly not in game.)" + }) + .addPart(EntityPassiveModel().apply { + name = "Blood Wells" + description = "You can summon blood wells for pyre, that allow you to heal your units health and mana." + }), + IdsEntity.FACTION_Iratek to EntityModel(IdsEntity.FACTION_Iratek, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Iratek" }), + IdsEntity.FACTION_Yul to EntityModel(IdsEntity.FACTION_Yul, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Yul" }), + + // Factions - Angelic + IdsEntity.FACTION_QRath to EntityModel(IdsEntity.FACTION_QRath, EntityType.Faction) + .addPart(EntityInfoModel().apply { + name = "Q'Rath" + notes = "Angelic faction that has adopted many humans into their ranks. They seek to bring more into their collective." + }) + .addPart(EntityPassiveModel().apply { + name = "Wards" + description = "Your units have an extra layer of health that is always (but slowly) regenerates. The regeneration is double on Hallowed Ground." + }), + IdsEntity.FACTION_YRiah to EntityModel(IdsEntity.FACTION_QRath, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "R'Raih" }), + IdsEntity.FACTION_ArkShai to EntityModel(IdsEntity.FACTION_QRath, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Ark'Shai" }), + + // Factions - Human + IdsEntity.FACTION_Jora to EntityModel(IdsEntity.FACTION_Jora, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Jora" }), + IdsEntity.FACTION_Telmetra to EntityModel(IdsEntity.FACTION_Telmetra, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Talmetra" }), + IdsEntity.FACTION_Kjor to EntityModel(IdsEntity.FACTION_Kjor, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Kjor" }), + + // Factions - Rae + IdsEntity.FACTION_Herlesh to EntityModel(IdsEntity.FACTION_Herlesh, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Herlesh" }), + + // Factions - Coalition + IdsEntity.FACTION_Khardu to EntityModel(IdsEntity.FACTION_Khardu, EntityType.Faction, true) + .addPart(EntityInfoModel().apply { name = "Khardu" }), + + // Factions - Neutral + IdsEntity.FACTION_Neutral to EntityModel(IdsEntity.FACTION_Neutral, EntityType.Faction) + .addPart(EntityInfoModel().apply { name = "Neutral" }), + + // Keys / Commands + IdsEntity.COMMAND_Attack to EntityModel(IdsEntity.COMMAND_Attack, EntityType.Command) + .addPart(EntityInfoModel().apply { name = "Attack"; description = "Makes selected units attack targeted area." }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; hotkeyGroup = "D" }), + IdsEntity.COMMAND_StandGround to EntityModel(IdsEntity.COMMAND_StandGround, EntityType.Command) + .addPart(EntityInfoModel().apply { name = "Stand Ground"; description = "Makes selected units stop moving." }) + .addPart(EntityHotkeyModel().apply { hotkey = "S"; hotkeyGroup = "D" }), + + // Starting Structures + IdsEntity.STARTING_Bastion to EntityModel(IdsEntity.STARTING_Bastion, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Bastion"; description = "Provides a fully upgraded base worth of alloy." + notes = "Revives in 40 seconds when destroyed." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Neutral }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 6f; requiresWorker = false; resource = ResourceType.Alloy; slots = 1f + totalAmount = 6000 + }) + .addPart(EntityVitalityModel().apply { health = 500; armor = ArmourType.Heavy }) + .addPart(EntityWeaponModel().apply { damage = 30; attacksPerSecond = 1.401f; targets = TargetType.All; range = 700 }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_BastionPassives }), + IdsEntity.STARTING_Tower to EntityModel(IdsEntity.STARTING_Tower, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Starting Tower" + notes = "Currently not in game. Can be upgraded to the factions pyre tower." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Neutral }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Neutral }) + .addPart(EntityVitalityModel().apply { health = 1000; defenseLayer = 500; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityWeaponModel().apply { + damage = 20; range = 800; attacksPerSecond = 1.124f; targets = TargetType.All + mediumDamage = 25; heavyDamage = 30 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Respite }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + // Starting Structures - Aru + IdsEntity.STARTING_TownHall_Aru to EntityModel(IdsEntity.STARTING_TownHall_Aru, EntityType.Building, true) + .addPart(EntityInfoModel().apply { name = "Grove Heart (Starting)"; descriptive = DescriptiveType.TownHall_Starting }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityVitalityModel().apply { health = 2000; defenseLayer = 400; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 1f; requiresWorker = true; resource = ResourceType.Alloy; slots = 6f + totalAmount = 6000 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_Rootway }), + + // Starting Structures - Q'Rath + IdsEntity.STARTING_TownHall_QRath to EntityModel(IdsEntity.STARTING_TownHall_QRath, EntityType.Building, true) + .addPart(EntityInfoModel().apply { name = "Acropolis (Starting)"; descriptive = DescriptiveType.TownHall_Starting }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityVitalityModel().apply { health = 1600; defenseLayer = 800; armor = ArmourType.Heavy; isStructure = true }) + .addPart(EntityHarvestModel().apply { + harvestedPerInterval = 6f; requiresWorker = false; resource = ResourceType.Alloy; slots = 1f + totalAmount = 6000 + }) + .addPart(EntityIdPassiveModel().apply { id = IdsEntity.PASSIVE_HallowedGround }), + + // Passives - Neutral + IdsEntity.PASSIVE_Detection to EntityModel(IdsEntity.PASSIVE_Detection, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Detection"; descriptive = DescriptiveType.Passive + description = """Unit can see all hidden units in its detection radius.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.Any }), + IdsEntity.PASSIVE_BastionPassives to EntityModel(IdsEntity.PASSIVE_BastionPassives, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "(Scouts and Pyre)"; descriptive = DescriptiveType.Passive + description = """Bastion generates one scout in 2 minutes, up to a max of 2 scouts. And generates pyre over time.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.Any }), + IdsEntity.PASSIVE_Respite to EntityModel(IdsEntity.PASSIVE_Respite, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Respite"; descriptive = DescriptiveType.Passive + description = """Nearby units will slowly heal after not attacking or being attacked for 10 seconds.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.Any }), + IdsEntity.PASSIVE_HarvestAlloy to EntityModel(IdsEntity.PASSIVE_HarvestAlloy, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Harvest Alloy"; descriptive = DescriptiveType.Passive + description = "This unit can harvest alloy." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.Any }), + + // Neutral + IdsEntity.NEUTRAL_PyreMiner to EntityModel(IdsEntity.NEUTRAL_PyreMiner, EntityType.Building) + .addPart(EntityInfoModel().apply { + name = "Pyre Miner"; description = "Passively harvest pyre." + notes = "" + }) + ) + } + + private fun getPassiveData(): Map { + return mapOf( + // Q'Rath Passives + + IdsEntity.PASSIVE_HallowedWarrior to EntityModel(IdsEntity.PASSIVE_HallowedWarrior, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hallowed Warrior"; descriptive = DescriptiveType.Ability + description = """Gains bonus shields when in Hallowed Ground""".trimIndent() + notes = "+20 Shields on Hallowed Ground." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_GreavesOfAhqar to EntityModel(IdsEntity.PASSIVE_GreavesOfAhqar, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Greaves Of Ahqar"; descriptive = DescriptiveType.Ability + description = """+75 Sipari Speed""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_GreavesOfAhqar; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_RelicOfTheWrathfulGaze to EntityModel(IdsEntity.PASSIVE_RelicOfTheWrathfulGaze, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Relic Of The Wrathful Gaze"; descriptive = DescriptiveType.Ability + description = """Increases Castigator range against air.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_RelicOfTheWrathfulGaze; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_WingsOfTheKenLatir to EntityModel(IdsEntity.PASSIVE_WingsOfTheKenLatir, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Wings of the Ken'Latir"; descriptive = DescriptiveType.Passive + description = """Increases the Warden's speed and shields significantly.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_WingsOfTheKenLatir; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_ExecutionRites to EntityModel(IdsEntity.PASSIVE_ExecutionRites, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Execution Rites"; descriptive = DescriptiveType.Passive + description = """Warden's attacks charge up to a hit that deals greatly increased damage.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_IconOfKhastEem to EntityModel(IdsEntity.PASSIVE_IconOfKhastEem, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Icon Of Khast'Eem"; descriptive = DescriptiveType.Passive + description = """Grants the Zentari shields and flat armor reduction.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_IconOfKhastEem; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_FaithCastBlades to EntityModel(IdsEntity.PASSIVE_FaithCastBlades, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Faith Cast Blades"; descriptive = DescriptiveType.Passive + description = """Increases the range of the Zentari's ranged weapon.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_FaithCastBlades; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_ThroneMovingShot to EntityModel(IdsEntity.PASSIVE_ThroneMovingShot, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Throne Moving Shot"; descriptive = DescriptiveType.Passive + description = "Thrones can attack while moving." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_SiroccoScript to EntityModel(IdsEntity.PASSIVE_SiroccoScript, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Sirocco Script Rites"; descriptive = DescriptiveType.Passive + description = """Increases the derish's movement speed""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_SiroccoScript }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_HallowingRites to EntityModel(IdsEntity.PASSIVE_HallowingRites, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hallowing Rites"; descriptive = DescriptiveType.Passive + description = """Ark Mother's creates Hallowed Ground on stabilize.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_HallowedRuin to EntityModel(IdsEntity.PASSIVE_HallowedRuin, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hallowed Ruin"; descriptive = DescriptiveType.Passive + description = """Hallowers have splash on attacks that leave an area of Hallowed Ground.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_RegentsWrath to EntityModel(IdsEntity.PASSIVE_RegentsWrath, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Regent's Wrath"; descriptive = DescriptiveType.Passive + description = """Sceptres gain energy when stabilized. They lose energy when moving. Energy is spent to have splash on attack.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_PsalmOfFire to EntityModel(IdsEntity.PASSIVE_PsalmOfFire, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Psalm Of Fire"; descriptive = DescriptiveType.Applies_Debuff + description = """Fire Singers deal damage over time against attacked targets.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_HallowedWeapons to EntityModel(IdsEntity.PASSIVE_HallowedWeapons, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hallowed Weapons"; descriptive = DescriptiveType.Applies_Debuff + description = """Gains 14 damage while in Hallowed Ground""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_Zeal to EntityModel(IdsEntity.PASSIVE_Zeal, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Zeal"; descriptive = DescriptiveType.Passive + description = "Increased attack speed to allied near Pillar of the Heavens" + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_HallowedGround to EntityModel(IdsEntity.PASSIVE_HallowedGround, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hallowed Ground"; descriptive = DescriptiveType.Passive + description = "This building generates Hallowed Ground." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + // Aru Passives + + IdsEntity.PASSIVE_WraithBowRange to EntityModel(IdsEntity.PASSIVE_WraithBowRange, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Wraith Bow Range"; descriptive = DescriptiveType.Ability + description = """Increases Wraith Bow range against air.""".trimIndent() + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_EoxBowstring; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_Rootway to EntityModel(IdsEntity.PASSIVE_Rootway, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Rootway"; descriptive = DescriptiveType.Passive + description = "Building generates Rootway." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_BehemothCapacity to EntityModel(IdsEntity.PASSIVE_BehemothCapacity, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Quitl Storage"; descriptive = DescriptiveType.Passive + description = "Unit stores quitl for attacks." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_QuitlStorage2 to EntityModel(IdsEntity.PASSIVE_QuitlStorage2, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Quitl Storage"; descriptive = DescriptiveType.Passive + description = "Unit stores more quitl for attacks." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_VitellineCysts; requirement = RequirementType.Research_Upgrade }), + + IdsEntity.PASSIVE_ExternalDigestion to EntityModel(IdsEntity.PASSIVE_ExternalDigestion, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "External Digestion"; descriptive = DescriptiveType.Passive + description = """Ichor attacks splash in a cone.""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_PursuitLigaments to EntityModel(IdsEntity.PASSIVE_PursuitLigaments, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Pursuit Ligaments"; descriptive = DescriptiveType.Passive + description = """Increases Ichor speed to 530 (+106).""".trimIndent() + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_PursuitLigaments; requirement = RequirementType.Research_Upgrade }), + + IdsEntity.PASSIVE_Temporary to EntityModel(IdsEntity.PASSIVE_Temporary, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Temporary"; descriptive = DescriptiveType.Passive + description = "This unit has a limited duration before it dies." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_Stalk to EntityModel(IdsEntity.PASSIVE_Stalk, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Stalk"; descriptive = DescriptiveType.Passive + description = """After remaining stationary for several seconds, gain Hidden 3 and a movement speed boost.""".trimIndent() + notes = "Lose hidden on attacking" + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_Ambush to EntityModel(IdsEntity.PASSIVE_Stalk, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Ambush"; descriptive = DescriptiveType.Passive + description = "This unit deals double damage when attacking from hidden." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_Ambush }), + + IdsEntity.PASSIVE_HiddenX to EntityModel(IdsEntity.PASSIVE_HiddenX, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Hidden X"; descriptive = DescriptiveType.Passive + description = "This unit cannot be seen unless enemies units are within X." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_FallenHarvest to EntityModel(IdsEntity.PASSIVE_FallenHarvest, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Fallen Harvest"; descriptive = DescriptiveType.Passive + description = "Incubator gets energy when nearby non-quitl units die." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_RestoreLifeblood to EntityModel(IdsEntity.PASSIVE_RestoreLifeblood, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Restore Lifeblood"; descriptive = DescriptiveType.Passive + description = "Quickly heals a nearby unit" + }) + .addPart(EntityProductionModel().apply { cooldown = 0.25f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_Transfusion to EntityModel(IdsEntity.PASSIVE_Transfusion, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Transfusion"; descriptive = DescriptiveType.Passive + description = "Spends mana to refill the mana of nearby units" + }) + .addPart(EntityProductionModel().apply { energy = 4; cooldown = 1f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_FortifiedIcons to EntityModel(IdsEntity.PASSIVE_FortifiedIcons, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Fortified Icons"; descriptive = DescriptiveType.Ability + description = """Increases Sipari shields and increases the bonus while in Hallowed Ground""".trimIndent() + notes = "+20 Shields, and +20 Shields on Hallowed Ground." + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_FortifiedIcons; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_MendingDecree to EntityModel(IdsEntity.PASSIVE_MendingDecree, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Mending Decree"; descriptive = DescriptiveType.Ability + description = "Heals a nearby allied unit." + }) + .addPart(EntityProductionModel().apply { energy = 10; cooldown = 3f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_GodstoneBulwark to EntityModel(IdsEntity.PASSIVE_GodstoneBulwark, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Godstone Bulkwark"; descriptive = DescriptiveType.Ability + description = "Grants +1 damage reduction." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + IdsEntity.PASSIVE_Invervention to EntityModel(IdsEntity.PASSIVE_Invervention, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Intervention"; descriptive = DescriptiveType.Ability + description = "The Saoshin releases healing energy. Allied units nearby heal over several seconds. This automatically activates when the Saoshin drops below 70 HP." + }) + .addPart(EntityProductionModel().apply { pyre = 70; cooldown = 5f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }), + + // Aru Passives (continued) + + IdsEntity.PASSIVE_BloodFrenzy to EntityModel(IdsEntity.PASSIVE_BloodFrenzy, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Blood Frenzy"; descriptive = DescriptiveType.Ability + description = "Thrums gain more attack speed for a short duration when a nearby allied Thrum kills an enemy unit." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_XacalDamage to EntityModel(IdsEntity.PASSIVE_XacalDamage, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Xacal Damage"; descriptive = DescriptiveType.Ability + description = "Xacal builds up charges for double damage overtime. These charges can be spent on attacking." + }) + .addPart(EntityRequirementModel().apply { id = IdsEntity.UPGRADE_EthericFibers; requirement = RequirementType.Research_Upgrade }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_XacalDefense to EntityModel(IdsEntity.PASSIVE_XacalDefense, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Xacal Defense"; descriptive = DescriptiveType.Ability + description = "Xacal take 1 less damage from all attacks." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_CastingFromBlood to EntityModel(IdsEntity.PASSIVE_CastingFromBlood, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Cast From Blood"; descriptive = DescriptiveType.Ability + description = "This unit can spend life to cast abilities when it doesn't have enough energy." + notes = "They must have at least one remaining hitpoint after to perform Cast From Blood." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_OssifyingSwarm to EntityModel(IdsEntity.PASSIVE_OssifyingSwarm, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Ossifying Swarm"; descriptive = DescriptiveType.Ability + description = "Reduces the movement speed and attack speed of enemies near your attack target." + notes = "10% movement and attack speed. Stacks up to 5 times." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_AaroxBurn to EntityModel(IdsEntity.PASSIVE_AaroxBurn, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Aarox Burn"; descriptive = DescriptiveType.Ability + description = "The aarox dies when attacking. Any units in its area of effect suffer damage over time." + notes = "Deals 75 damage over 3 seconds." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_QuenchingScythes to EntityModel(IdsEntity.PASSIVE_QuenchingScythes, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Quenching Scythes"; descriptive = DescriptiveType.Ability + description = "Recovers 5 life when dealing damage, or 10 mana if life is full." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_EngorgedArteries to EntityModel(IdsEntity.PASSIVE_EngorgedArteries, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Engorged Arteries"; descriptive = DescriptiveType.Ability + description = "Grants +2 range when deployed on rootway." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_ProjectileGestation to EntityModel(IdsEntity.PASSIVE_ProjectileGestation, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Projectile Gestation"; descriptive = DescriptiveType.Ability + description = "Fires a quitl at a target enemy unit." + }) + .addPart(EntityProductionModel().apply { energy = 35; cooldown = 2.5f }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_GuidingAmber to EntityModel(IdsEntity.PASSIVE_GuidingAmber, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Guiding Amber"; descriptive = DescriptiveType.Ability + description = "Units hit by this attack takes +1 damage for a few seconds, to a maximum of +4." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }), + + IdsEntity.PASSIVE_FireQuitl to EntityModel(IdsEntity.PASSIVE_FireQuitl, EntityType.Passive) + .addPart(EntityInfoModel().apply { + name = "Spawn Quitl"; descriptive = DescriptiveType.Ability + description = "Unit spawns Quitl on attack." + notes = "Quitl deals 99 damage over it's life span of 8 seconds." + }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + ) + } + + private fun getResearchData(): Map { + return mapOf( + // Upgrades + // Q'Rath + IdsEntity.UPGRADE_GreavesOfAhqar to EntityModel(IdsEntity.UPGRADE_GreavesOfAhqar, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Greaves Of Ahqar" + descriptive = DescriptiveType.Upgrade + description = "Increases the Sipari speed by 75." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 100; producedBy = IdsEntity.BUILDING_Reliquary + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Production_Building + }) + .addPart(EntityVanguardReplacedModel().apply { + immortalId = IdsEntity.IMMORTAL_Orzum + replacedById = IdsEntity.UPGRADE_FaithCastBlades + }), + + IdsEntity.UPGRADE_RadiantWard to EntityModel(IdsEntity.UPGRADE_RadiantWard, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Research Radiant Ward" + descriptive = DescriptiveType.Upgrade + description = "Unlocks the dervish's Radiant Ward ability" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; holdSpace = true; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 80; ether = 80; buildTime = 34; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_FortifiedIcons to EntityModel(IdsEntity.UPGRADE_FortifiedIcons, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Fortified Icons" + descriptive = DescriptiveType.Upgrade + description = """ + Sipari get more Shields. + + Bonus Shields from Hallowed Ground + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 125; buildTime = 60; producedBy = IdsEntity.BUILDING_EyeOfAros + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_EyeOfAros + requirement = RequirementType.Production_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Production_Building + }) + .addPart(EntityVanguardReplacedModel().apply { + immortalId = IdsEntity.IMMORTAL_Orzum + replacedById = IdsEntity.UPGRADE_IconOfKhastEem + }), + + IdsEntity.UPGRADE_FaithCastBlades to EntityModel(IdsEntity.UPGRADE_FaithCastBlades, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Faith-Cast Blades" + descriptive = DescriptiveType.Upgrade + description = """ + Zentari get more range attack when in Hallowed Ground. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 50; ether = 125; buildTime = 60; producedBy = IdsEntity.BUILDING_Reliquary + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Research_Building + }) + .addPart(EntityVanguardAddedModel().apply { + replaceId = IdsEntity.UPGRADE_GreavesOfAhqar + immortalId = IdsEntity.IMMORTAL_Orzum + }), + + IdsEntity.UPGRADE_RelicOfTheWrathfulGaze to EntityModel(IdsEntity.UPGRADE_RelicOfTheWrathfulGaze, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Relic Of The Wrathful Gaze" + descriptive = DescriptiveType.Upgrade + description = "Increases the Castigator's anti-air weapon range." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 75; ether = 75; buildTime = 29; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_PsalmOfFire to EntityModel(IdsEntity.UPGRADE_PsalmOfFire, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Psalm of Fire" + descriptive = DescriptiveType.Upgrade + description = "Fire Singers get area damage." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "S"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 200; buildTime = 64 + producedBy = IdsEntity.BUILDING_KeeperOfTheHardenedFlames + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_KeeperOfTheHardenedFlames + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_WindStep to EntityModel(IdsEntity.UPGRADE_WindStep, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Windstep" + descriptive = DescriptiveType.Upgrade + description = "Unlocks windstep." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 100; buildTime = 75; producedBy = IdsEntity.BUILDING_MonasteryOfIzur + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_ZephyrRange to EntityModel(IdsEntity.UPGRADE_ZephyrRange, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Zephyr Range" + descriptive = DescriptiveType.Upgrade + description = "Increases Zephyr's range by 100." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 50; buildTime = 50; producedBy = IdsEntity.BUILDING_Reliquary + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Reliquary + requirement = RequirementType.Research_Building + }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_WindStep }) + .addPart(EntityIdUpgradeModel().apply { id = IdsEntity.UPGRADE_ZephyrRange }), + + IdsEntity.UPGRADE_SiroccoScript to EntityModel(IdsEntity.UPGRADE_SiroccoScript, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Sirocco Script" + descriptive = DescriptiveType.Upgrade + description = "Grant's the Dervish Sirocco Script" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 125; buildTime = 60; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_IconOfTheEnduringVigil to EntityModel(IdsEntity.UPGRADE_IconOfTheEnduringVigil, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Icon of the Enduring Vigil" + descriptive = DescriptiveType.Upgrade + description = "The Dervish's Radiant Wards become permanent." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 34; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_AbsolverHealthUpgrade to EntityModel(IdsEntity.UPGRADE_AbsolverHealthUpgrade, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Absolver Health Upgrade" + descriptive = DescriptiveType.Upgrade + description = "Absolvers get more HP and damage reductions" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 150; ether = 150; buildTime = 90; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_HouseOfFadingSaints + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_Awestrike to EntityModel(IdsEntity.UPGRADE_Awestrike, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Awestrike" + descriptive = DescriptiveType.Upgrade + description = "Unlocks a damage spell for Shar'u" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 150; buildTime = 45; producedBy = IdsEntity.BUILDING_HouseOfFadingSaints + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_EyeOfAros + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_IconOfKhastEem to EntityModel(IdsEntity.UPGRADE_IconOfKhastEem, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Icon of Khast'Eem" + descriptive = DescriptiveType.Upgrade + description = "Grants the Zentari shields and flat armor reduction." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; holdSpace = true; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 43; producedBy = IdsEntity.BUILDING_EyeOfAros + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_EyeOfAros + requirement = RequirementType.Production_Building + }) + .addPart(EntityVanguardAddedModel().apply { + replaceId = IdsEntity.UPGRADE_FortifiedIcons + immortalId = IdsEntity.IMMORTAL_Orzum + }), + + IdsEntity.UPGRADE_WingsOfTheKenLatir to EntityModel(IdsEntity.UPGRADE_WingsOfTheKenLatir, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Wings of the Ken'Latir" + descriptive = DescriptiveType.Upgrade + description = "Wardens use their empowered attacks more often." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; holdSpace = true; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 150; ether = 100; buildTime = 60; producedBy = IdsEntity.BUILDING_BearerOfTheCrown + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_EyeOfAros + requirement = RequirementType.Production_Building + }), + + IdsEntity.UPGRADE_TitheBlades to EntityModel(IdsEntity.UPGRADE_TitheBlades, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Tithe Blades" + descriptive = DescriptiveType.Upgrade + description = "Unlocks a utility ability for Thrones." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; holdSpace = true; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_QRath }) + .addPart(EntityProductionModel().apply { + alloy = 200; ether = 200; buildTime = 120; producedBy = IdsEntity.BUILDING_BearerOfTheCrown + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_BearerOfTheCrown + requirement = RequirementType.Production_Building + }), + + // Upgrades + // Aru + IdsEntity.UPGRADE_Offering to EntityModel(IdsEntity.UPGRADE_Offering, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Offering" + descriptive = DescriptiveType.Upgrade + description = "Unlocks Offering" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AltarOfTheWorthy + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Production_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 75; buildTime = 60; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_PursuitLigaments to EntityModel(IdsEntity.UPGRADE_PursuitLigaments, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Pursuit Ligaments" + descriptive = DescriptiveType.Upgrade + description = """ + Ichors get more movement speed. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 80; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_ResinantSpeed to EntityModel(IdsEntity.UPGRADE_ResinantSpeed, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Resinant Speed" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 60; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_RootShepherdHidden to EntityModel(IdsEntity.UPGRADE_RootShepherdHidden, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Root Shepherd Hidden" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 60; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_RootShepherdSpeed to EntityModel(IdsEntity.UPGRADE_RootShepherdSpeed, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Root Shepherd Speed" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 80; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_EthericFibers to EntityModel(IdsEntity.UPGRADE_EthericFibers, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Etheric Fibers" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "Q"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 75; ether = 100; buildTime = 75; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_ObstructingSwarm to EntityModel(IdsEntity.UPGRADE_ObstructingSwarm, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Obstructing Swarm" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 150; ether = 100; buildTime = 100; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_Hematoma to EntityModel(IdsEntity.UPGRADE_Hematoma, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Hematoma" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_AmberWomb + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 125; buildTime = 45; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_VitellineCysts to EntityModel(IdsEntity.UPGRADE_VitellineCysts, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Vitelline Cysts" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_DeepNest + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 200; ether = 200; buildTime = 46; producedBy = IdsEntity.BUILDING_DeepNest + }), + + IdsEntity.UPGRADE_HyperAdrenoceptors to EntityModel(IdsEntity.UPGRADE_HyperAdrenoceptors, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Hyper Adrenoceptors" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_DeepNest + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 100; buildTime = 60; producedBy = IdsEntity.BUILDING_DeepNest + }), + + IdsEntity.UPGRADE_EoxBowstring to EntityModel(IdsEntity.UPGRADE_EoxBowstring, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Eox Bowstring" + descriptive = DescriptiveType.Upgrade + description = "Increase's the range of the Wraith Bow anti-air attack." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_Neurocyte + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 50; ether = 100; buildTime = 29; producedBy = IdsEntity.BUILDING_Neurocyte + }), + + IdsEntity.UPGRADE_SporeBurst to EntityModel(IdsEntity.UPGRADE_SporeBurst, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Spore Burst" + descriptive = DescriptiveType.Upgrade + description = "Aerovores and Omnivores get area damage vs air units." + }) + .addPart(EntityHotkeyModel().apply { hotkey = "S"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RootCradle + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 150; buildTime = 64; producedBy = IdsEntity.BUILDING_RootCradle + }), + + IdsEntity.UPGRADE_Ambush to EntityModel(IdsEntity.UPGRADE_Ambush, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Research Ambush" + descriptive = DescriptiveType.Upgrade + description = """ + Bone Stalkers get double damage for a few seconds after attack while Hidden. + """.trimIndent() + }) + .addPart(EntityHotkeyModel().apply { hotkey = "W"; hotkeyGroup = "X"; holdSpace = false }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 75; buildTime = 60; producedBy = IdsEntity.BUILDING_RedVale + }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }), + + IdsEntity.UPGRADE_BloodPlague to EntityModel(IdsEntity.UPGRADE_BloodPlague, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Blood Plague" + descriptive = DescriptiveType.Upgrade + description = "Unlocks Blood Plague" + }) + .addPart(EntityHotkeyModel().apply { hotkey = "R"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 150; buildTime = 100; producedBy = IdsEntity.BUILDING_RedVale + }) + .addPart(EntityVanguardReplacedModel().apply { + immortalId = IdsEntity.IMMORTAL_Xol + replacedById = IdsEntity.ABILITY_BirthingStorm + }), + + IdsEntity.UPGRADE_GodphageDamage to EntityModel(IdsEntity.UPGRADE_GodphageDamage, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Godphage Damage" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "E"; hotkeyGroup = "X" }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_DeepNest + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 250; ether = 250; buildTime = 100; producedBy = IdsEntity.BUILDING_DeepNest + }), + + IdsEntity.UPGRADE_BirthingStorm to EntityModel(IdsEntity.UPGRADE_BirthingStorm, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Birthing Storm" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "F"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 100; ether = 150; buildTime = 100; producedBy = IdsEntity.BUILDING_RedVale + }) + .addPart(EntityVanguardAddedModel().apply { + immortalId = IdsEntity.IMMORTAL_Mala + replaceId = IdsEntity.ABILITY_BloodPlague + }), + + IdsEntity.UPGRADE_GENERIC_Attack1 to EntityModel(IdsEntity.UPGRADE_GENERIC_Attack1, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Aru Attack Level 1" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "A"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 125; buildTime = 114; producedBy = IdsEntity.BUILDING_RootCradle + }), + + IdsEntity.UPGRADE_GENERIC_Defense1 to EntityModel(IdsEntity.UPGRADE_GENERIC_Defense1, EntityType.Tech) + .addPart(EntityInfoModel().apply { + name = "Aru Defense Level 1" + descriptive = DescriptiveType.Upgrade + }) + .addPart(EntityHotkeyModel().apply { hotkey = "S"; hotkeyGroup = "X"; holdSpace = true }) + .addPart(EntityFactionModel().apply { faction = IdsEntity.FACTION_Aru }) + .addPart(EntityRequirementModel().apply { + id = IdsEntity.BUILDING_RedVale + requirement = RequirementType.Research_Building + }) + .addPart(EntityProductionModel().apply { + alloy = 125; ether = 125; buildTime = 114; producedBy = IdsEntity.BUILDING_RootCradle + }) + ) + } + } + + + diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/IdsEntity.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/IdsEntity.kt index 69eb92d..6852afd 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/IdsEntity.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/data/IdsEntity.kt @@ -156,6 +156,7 @@ object IdsEntity { const val PASSIVE_RegentsWrath = "f111f004-6548-4430-9d13-ef44ab108ae7" const val PASSIVE_PsalmOfFire = "PASSIVE_PsalmOfFire" const val PASSIVE_HallowedWeapons = "f9ac4b3e-d02d-42d4-8d9d-beb9c5d7edcb" + const val PASSIVE_Maledictions = "PASSIVE_Maledictions" const val PASSIVE_Zeal = "62c4942b-5578-422d-8d4e-d1789f4efa68" const val PASSIVE_HallowedGround = "bdb28984-246f-4642-84ab-9e83c02b3e2e" const val PASSIVE_Rootway = "46768d4a-5047-4973-b5ca-995cda25ee8d" diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/types/ArmorType.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/types/ArmorType.kt new file mode 100644 index 0000000..82f311d --- /dev/null +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/data/models/entity/types/ArmorType.kt @@ -0,0 +1,7 @@ +package ca.jonathanmccaffrey.igp.data.models.entity.types + +object ArmorType { + const val Light = "Light" + const val Medium = "Medium" + const val Heavy = "Heavy" +} diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/BuildCalculatorScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/BuildCalculatorScreen.kt index a1dd4dc..ef11145 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/BuildCalculatorScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/BuildCalculatorScreen.kt @@ -11,10 +11,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -25,15 +32,29 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityType +import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity import ca.jonathanmccaffrey.igp.di.ServiceLocator +import androidx.compose.material3.ExperimentalMaterial3Api +@OptIn(ExperimentalMaterial3Api::class) @Composable fun BuildCalculatorScreen() { val timingService = ServiceLocator.timingService val buildOrderService = ServiceLocator.buildOrderService + val economyService = ServiceLocator.economyService + val keyService = ServiceLocator.keyService + val immortalSelectionService = ServiceLocator.immortalSelectionService + val entityFilterService = ServiceLocator.entityFilterService var attackTime by remember { mutableStateOf(timingService.getAttackTime()) } var travelTime by remember { mutableStateOf(timingService.getTravelTime()) } + var factionExpanded by remember { mutableStateOf(false) } + var immortalExpanded by remember { mutableStateOf(false) } + val factionChoices = remember { entityFilterService.getFactionChoices() } + var selectedFaction by remember { mutableStateOf(immortalSelectionService.getFaction()) } + val immortalChoices = remember { entityFilterService.getImmortalChoices() } + var selectedImmortal by remember { mutableStateOf(immortalSelectionService.getImmortal()) } LaunchedEffect(Unit) { timingService.onChange.collect { @@ -46,12 +67,23 @@ fun BuildCalculatorScreen() { buildOrderService.onChange.collect { } } + LaunchedEffect(Unit) { + economyService.onChange.collect { } + } + Column( modifier = Modifier.fillMaxSize().padding(16.dp) ) { + Text( + text = "Build Calculator", + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(Modifier.height(12.dp)) + Card( modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Row( modifier = Modifier.fillMaxWidth().padding(16.dp), @@ -65,6 +97,100 @@ fun BuildCalculatorScreen() { Spacer(Modifier.height(12.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + ExposedDropdownMenuBox( + expanded = factionExpanded, + onExpandedChange = { factionExpanded = it }, + modifier = Modifier.weight(1f) + ) { + OutlinedTextField( + value = selectedFaction, + onValueChange = {}, + readOnly = true, + label = { Text("Faction") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = factionExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = factionExpanded, + onDismissRequest = { factionExpanded = false } + ) { + factionChoices.filter { it != IdsEntity.Any && it != IdsEntity.None }.forEach { faction -> + DropdownMenuItem( + text = { Text(faction) }, + onClick = { + selectedFaction = faction + factionExpanded = false + immortalSelectionService.selectFaction(faction) + } + ) + } + } + } + + ExposedDropdownMenuBox( + expanded = immortalExpanded, + onExpandedChange = { immortalExpanded = it }, + modifier = Modifier.weight(1f) + ) { + OutlinedTextField( + value = selectedImmortal, + onValueChange = {}, + readOnly = true, + label = { Text("Immortal") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = immortalExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = immortalExpanded, + onDismissRequest = { immortalExpanded = false } + ) { + immortalChoices.forEach { immortal -> + DropdownMenuItem( + text = { Text(immortal) }, + onClick = { + selectedImmortal = immortal + immortalExpanded = false + immortalSelectionService.selectImmortal(immortal) + } + ) + } + } + } + } + + Spacer(Modifier.height(12.dp)) + + val economyData = economyService.getOverTime().let { list -> + if (list.isNotEmpty()) { + val last = list.last() + list.firstOrNull { it.interval == buildOrderService.getLastRequestInterval() } ?: last + } else null + } + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Economy State", style = MaterialTheme.typography.titleSmall) + if (economyData != null) { + Text("Alloy: ${"%.1f".format(economyData.alloy)}", style = MaterialTheme.typography.bodySmall) + Text("Ether: ${"%.1f".format(economyData.ether)}", style = MaterialTheme.typography.bodySmall) + Text("Pyre: ${"%.1f".format(economyData.pyre)}", style = MaterialTheme.typography.bodySmall) + Text("Workers: ${economyData.workerCount} (Busy: ${economyData.busyWorkerCount}, Creating: ${economyData.creatingWorkerCount})", style = MaterialTheme.typography.bodySmall) + Text("Harvest Points: ${economyData.harvestPoints.size}", style = MaterialTheme.typography.bodySmall) + } else { + Text("No economy data available", style = MaterialTheme.typography.bodySmall) + } + } + } + + Spacer(Modifier.height(12.dp)) + Text( text = "Build Order Timeline", style = MaterialTheme.typography.titleMedium @@ -74,8 +200,8 @@ fun BuildCalculatorScreen() { modifier = Modifier.weight(1f).padding(vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(6.dp) ) { - val allOrders = buildOrderService.getOrders() - items(allOrders.entries.toList()) { (interval, entities) -> + val allOrders = buildOrderService.startedOrders + items(allOrders.entries.sortedBy { it.key }.toList()) { (interval, entities) -> Card( modifier = Modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) @@ -108,5 +234,43 @@ fun BuildCalculatorScreen() { Text("Reset") } } + + Spacer(Modifier.height(12.dp)) + + HorizontalDivider() + + Spacer(Modifier.height(8.dp)) + + Text( + text = "Economy Over Time", + style = MaterialTheme.typography.titleMedium + ) + + Spacer(Modifier.height(8.dp)) + + val economyOverTime = economyService.getOverTime() + Column( + modifier = Modifier.height(200.dp).verticalScroll(rememberScrollState()) + ) { + economyOverTime.take(60).forEach { econ -> + Card( + modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text("T=${econ.interval}", style = MaterialTheme.typography.labelSmall) + Text("A:${"%.0f".format(econ.alloy)}", style = MaterialTheme.typography.labelSmall) + Text("E:${"%.0f".format(econ.ether)}", style = MaterialTheme.typography.labelSmall) + Text("P:${"%.0f".format(econ.pyre)}", style = MaterialTheme.typography.labelSmall) + Text("W:${econ.workerCount}", style = MaterialTheme.typography.labelSmall) + } + } + } + } } } diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataCollectionScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataCollectionScreen.kt index 11b3f8d..0a50837 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataCollectionScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataCollectionScreen.kt @@ -1,8 +1,13 @@ package ca.jonathanmccaffrey.igp.ui.screens import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,10 +25,23 @@ fun DataCollectionScreen() { text = "Data Collection", style = MaterialTheme.typography.headlineMedium ) + Spacer(Modifier.height(16.dp)) Text( - text = "Data collection settings and preferences.", - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(top = 16.dp) + text = "Data collection is not implemented on mobile.", + style = MaterialTheme.typography.bodyLarge ) + Spacer(Modifier.height(12.dp)) + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Text( + text = "Check the Permissions page to manage your privacy settings.", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(16.dp) + ) + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataTablesScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataTablesScreen.kt index 31e26f8..36abd0d 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataTablesScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DataTablesScreen.kt @@ -1,45 +1,316 @@ package ca.jonathanmccaffrey.igp.ui.screens +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel +import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityMovementModel +import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityVitalityModel +import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityWeaponModel @Composable fun DataTablesScreen() { + val entities = remember { EntityModel.getList() } + var selectedTabIndex by remember { mutableStateOf(0) } + val tabTitles = listOf("Weapons", "Production", "Health", "Movement") + Column( - modifier = Modifier.fillMaxSize().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.fillMaxSize().padding(16.dp) ) { Text( text = "Data Tables", style = MaterialTheme.typography.headlineMedium ) - Text( - text = "This tool is incomplete.", - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(top = 16.dp) - ) + + Spacer(Modifier.height(4.dp)) Card( - modifier = Modifier.fillMaxWidth().padding(top = 12.dp), + modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer + containerColor = MaterialTheme.colorScheme.tertiaryContainer ) ) { Text( - text = "Errors Present / Incomplete Data", + text = "Incomplete feature for easily comparing unit stats. Some data may be missing.", style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(16.dp) ) } + + Spacer(Modifier.height(8.dp)) + + ScrollableTabRow(selectedTabIndex = selectedTabIndex) { + tabTitles.forEachIndexed { index, title -> + Tab( + selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + text = { Text(title) } + ) + } + } + + HorizontalDivider() + + when (selectedTabIndex) { + 0 -> WeaponsTab(entities) + 1 -> ProductionTab(entities) + 2 -> HealthTab(entities) + 3 -> MovementTab(entities) + } + } +} + +@Composable +private fun WeaponsTab(entities: List) { + val withWeapons = entities.filter { it.weapons().isNotEmpty() } + + LazyColumn( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(withWeapons) { entity -> + WeaponCard(entity) + } + } +} + +@Composable +private fun WeaponCard(entity: EntityModel) { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = entity.info().name, + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + entity.weapons().forEachIndexed { index, weapon -> + if (index > 0) Spacer(Modifier.height(4.dp)) + WeaponRow(weapon) + } + } + } +} + +@Composable +private fun WeaponRow(weapon: EntityWeaponModel) { + val dps = weapon.damage * weapon.attacksPerSecond + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Range: ${weapon.range}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "Damage: ${weapon.damage}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "DPS: ${"%.1f".format(dps)}", + style = MaterialTheme.typography.bodySmall + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Targets: ${weapon.targets}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "HasSplash: ${if (weapon.hasSplash) "Yes" else "No"}", + style = MaterialTheme.typography.bodySmall + ) + } + } +} + +@Composable +private fun ProductionTab(entities: List) { + val withProduction = entities.filter { entity -> + val p = entity.production() + p != null && (p.alloy != 0 || p.buildTime != 0) + } + + LazyColumn( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(withProduction) { entity -> + ProductionCard(entity) + } + } +} + +@Composable +private fun ProductionCard(entity: EntityModel) { + val prod = entity.production()!! + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = entity.info().name, + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Alloy: ${prod.alloy}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "Ether: ${prod.ether}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "BuildTime: ${prod.buildTime}", + style = MaterialTheme.typography.bodySmall + ) + } + } + } +} + +@Composable +private fun HealthTab(entities: List) { + val withVitality = entities.filter { it.entityParts.any { p -> p is EntityVitalityModel } } + + LazyColumn( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(withVitality) { entity -> + HealthCard(entity) + } + } +} + +@Composable +private fun HealthCard(entity: EntityModel) { + val vit = entity.vitality() + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = entity.info().name, + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Health: ${vit.health}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "Armor: ${vit.armor}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "Defense: ${vit.defense}", + style = MaterialTheme.typography.bodySmall + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "DefenseLayer: ${vit.defenseLayer}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "IsStructure: ${if (vit.isStructure) "Yes" else "No"}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "IsEtheric: ${if (vit.isEtheric) "Yes" else "No"}", + style = MaterialTheme.typography.bodySmall + ) + } + } + } +} + +@Composable +private fun MovementTab(entities: List) { + val withMovement = entities.filter { it.entityParts.any { p -> p is EntityMovementModel } } + + LazyColumn( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(withMovement) { entity -> + MovementCard(entity) + } + } +} + +@Composable +private fun MovementCard(entity: EntityModel) { + val mov = entity.movement() + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = entity.info().name, + style = MaterialTheme.typography.titleSmall + ) + Spacer(Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Speed: ${mov.speed}", + style = MaterialTheme.typography.bodySmall + ) + Text( + text = "Movement: ${mov.movement}", + style = MaterialTheme.typography.bodySmall + ) + } + } } } diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DatabaseScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DatabaseScreen.kt index 1c84b39..bc24054 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DatabaseScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/DatabaseScreen.kt @@ -1,5 +1,6 @@ package ca.jonathanmccaffrey.igp.ui.screens +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,12 +11,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.FilterChip +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -27,20 +27,19 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel +import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity +import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityType as EntityTypeConst import ca.jonathanmccaffrey.igp.di.ServiceLocator import ca.jonathanmccaffrey.igp.services.website.EntityFilterEvent -@OptIn(ExperimentalMaterial3Api::class) @Composable fun DatabaseScreen() { val filterService = ServiceLocator.entityFilterService - var factionExpanded by remember { mutableStateOf(false) } - var immortalExpanded by remember { mutableStateOf(false) } - var entityTypeExpanded by remember { mutableStateOf(false) } - var factionText by remember { mutableStateOf(filterService.getFactionType()) } - var immortalText by remember { mutableStateOf(filterService.getImmortalType()) } - var entityTypeText by remember { mutableStateOf(filterService.getEntityType()) } + var factionType by remember { mutableStateOf(filterService.getFactionType()) } + var immortalType by remember { mutableStateOf(filterService.getImmortalType()) } + var entityType by remember { mutableStateOf(filterService.getEntityType()) } var searchText by remember { mutableStateOf(filterService.getSearchText()) } val factionChoices = remember { filterService.getFactionChoices() } @@ -50,18 +49,24 @@ fun DatabaseScreen() { LaunchedEffect(Unit) { filterService.onChange.collect { event -> when (event) { - EntityFilterEvent.OnRefreshFaction -> - factionText = filterService.getFactionType() - EntityFilterEvent.OnRefreshImmortal -> - immortalText = filterService.getImmortalType() - EntityFilterEvent.OnRefreshEntity -> - entityTypeText = filterService.getEntityType() - EntityFilterEvent.OnRefreshSearch -> - searchText = filterService.getSearchText() + EntityFilterEvent.OnRefreshFaction -> factionType = filterService.getFactionType() + EntityFilterEvent.OnRefreshImmortal -> immortalType = filterService.getImmortalType() + EntityFilterEvent.OnRefreshEntity -> entityType = filterService.getEntityType() + EntityFilterEvent.OnRefreshSearch -> searchText = filterService.getSearchText() } } } + val allEntities = remember { EntityModel.getList() } + + val filteredEntities = allEntities.filter { entity -> + val matchesFaction = factionType == IdsEntity.Any || entity.faction()?.faction == factionType + val matchesImmortal = immortalType == IdsEntity.Any || entity.vanguardAdded()?.immortalId == immortalType + val matchesEntityType = entityType == EntityTypeConst.Any || entity.entityType == entityType + val matchesSearch = searchText.isEmpty() || entity.getName().contains(searchText, ignoreCase = true) + matchesFaction && matchesImmortal && matchesEntityType && matchesSearch + } + Column( modifier = Modifier.fillMaxSize().padding(16.dp) ) { @@ -69,121 +74,163 @@ fun DatabaseScreen() { text = "Database", style = MaterialTheme.typography.headlineMedium ) + + Spacer(Modifier.height(4.dp)) + + Text( + text = "Entity data from current game version", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + Text( + text = "Faction", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Row( + modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + factionChoices.forEach { choice -> + FilterChip( + selected = factionType == choice, + onClick = { filterService.selectFactionType(choice) }, + label = { Text(choice) } + ) + } + } + + Spacer(Modifier.height(8.dp)) + + Text( + text = "Immortal", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Row( + modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + immortalChoices.forEach { choice -> + FilterChip( + selected = immortalType == choice, + onClick = { filterService.selectImmortalType(choice) }, + label = { Text(choice) } + ) + } + } + + Spacer(Modifier.height(8.dp)) + + Text( + text = "Entity Type", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Row( + modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + entityChoices.forEach { choice -> + FilterChip( + selected = entityType == choice, + onClick = { filterService.selectEntityType(choice) }, + label = { Text(choice) } + ) + } + } + Spacer(Modifier.height(12.dp)) - ExposedDropdownMenuBox( - expanded = factionExpanded, - onExpandedChange = { factionExpanded = !factionExpanded } - ) { - OutlinedTextField( - value = factionText, - onValueChange = {}, - readOnly = true, - label = { Text("Faction") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = factionExpanded) }, - modifier = Modifier.fillMaxWidth().menuAnchor() - ) - ExposedDropdownMenu( - expanded = factionExpanded, - onDismissRequest = { factionExpanded = false } - ) { - factionChoices.forEach { choice -> - DropdownMenuItem( - text = { Text(choice) }, - onClick = { - factionText = choice - filterService.selectFactionType(choice) - factionExpanded = false - } - ) - } - } - } - Spacer(Modifier.height(8.dp)) - - ExposedDropdownMenuBox( - expanded = immortalExpanded, - onExpandedChange = { immortalExpanded = !immortalExpanded } - ) { - OutlinedTextField( - value = immortalText, - onValueChange = {}, - readOnly = true, - label = { Text("Immortal") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = immortalExpanded) }, - modifier = Modifier.fillMaxWidth().menuAnchor() - ) - ExposedDropdownMenu( - expanded = immortalExpanded, - onDismissRequest = { immortalExpanded = false } - ) { - immortalChoices.forEach { choice -> - DropdownMenuItem( - text = { Text(choice) }, - onClick = { - immortalText = choice - filterService.selectImmortalType(choice) - immortalExpanded = false - } - ) - } - } - } - Spacer(Modifier.height(8.dp)) - - ExposedDropdownMenuBox( - expanded = entityTypeExpanded, - onExpandedChange = { entityTypeExpanded = !entityTypeExpanded } - ) { - OutlinedTextField( - value = entityTypeText, - onValueChange = {}, - readOnly = true, - label = { Text("Entity Type") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = entityTypeExpanded) }, - modifier = Modifier.fillMaxWidth().menuAnchor() - ) - ExposedDropdownMenu( - expanded = entityTypeExpanded, - onDismissRequest = { entityTypeExpanded = false } - ) { - entityChoices.forEach { choice -> - DropdownMenuItem( - text = { Text(choice) }, - onClick = { - entityTypeText = choice - filterService.selectEntityType(choice) - entityTypeExpanded = false - } - ) - } - } - } - Spacer(Modifier.height(8.dp)) - OutlinedTextField( value = searchText, onValueChange = { filterService.enterSearchText(it) }, label = { Text("Search") }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(8.dp)) + + Text( + text = "${filteredEntities.size} entities found", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) - Spacer(Modifier.height(12.dp)) LazyColumn( + modifier = Modifier.weight(1f).padding(top = 4.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(10) { index -> - Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Text( - text = "Entity placeholder $index", - modifier = Modifier.padding(16.dp), - style = MaterialTheme.typography.bodyLarge - ) - } + items(filteredEntities) { entity -> + EntityCard(entity = entity) } } } } + +@Composable +private fun EntityCard(entity: EntityModel) { + val info = entity.info() + val faction = entity.getFaction() + val tierModel = entity.tier() + val supplyModel = entity.supply() + val vitalityModel = entity.vitality() + + val statsLine = buildString { + append("HP: ${vitalityModel.health}") + if (supplyModel != null) { + append(" | Supply: ${supplyModel.takes}/${supplyModel.grants}") + } + val weapons = entity.weapons() + if (weapons.isNotEmpty()) { + val totalDps = weapons.sumOf { it.damagePerSecond().toDouble() } + append(" | DPS: ${"%.1f".format(totalDps)}") + } + } + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = info.name, + style = MaterialTheme.typography.titleSmall + ) + Text( + text = "T${tierModel.tier.toInt()}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary + ) + } + Spacer(Modifier.height(4.dp)) + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = faction, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = entity.entityType, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Spacer(Modifier.height(4.dp)) + Text( + text = statsLine, + style = MaterialTheme.typography.bodySmall + ) + } + } +} diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/EconomyComparisonScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/EconomyComparisonScreen.kt index 871e804..58694bc 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/EconomyComparisonScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/EconomyComparisonScreen.kt @@ -1,15 +1,21 @@ package ca.jonathanmccaffrey.igp.ui.screens +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -21,22 +27,35 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import ca.jonathanmccaffrey.igp.di.ServiceLocator @Composable fun EconomyComparisonScreen() { - val comparisonService = ServiceLocator.economyComparisonService + val service = ServiceLocator.economyComparisonService - var player1Faction by remember { mutableStateOf(comparisonService.getFaction(1)) } - var player2Faction by remember { mutableStateOf(comparisonService.getFaction(2)) } - var player1TownHalls by remember { mutableStateOf(comparisonService.getTownHallCount(1).toString()) } - var player2TownHalls by remember { mutableStateOf(comparisonService.getTownHallCount(2).toString()) } + var p1Faction by remember { mutableStateOf(service.getFaction(0)) } + var p2Faction by remember { mutableStateOf(service.getFaction(1)) } + var p1TownHalls by remember { mutableStateOf(service.getTownHallCount(0).toString()) } + var p2TownHalls by remember { mutableStateOf(service.getTownHallCount(1).toString()) } + var p1Times by remember { mutableStateOf(service.getTownHallBuildTimes(0)) } + var p2Times by remember { mutableStateOf(service.getTownHallBuildTimes(1)) } LaunchedEffect(Unit) { - comparisonService.onChange.collect { } + service.onChange.collect { + p1Faction = service.getFaction(0) + p2Faction = service.getFaction(1) + p1TownHalls = service.getTownHallCount(0).toString() + p2TownHalls = service.getTownHallCount(1).toString() + p1Times = service.getTownHallBuildTimes(0) + p2Times = service.getTownHallBuildTimes(1) + } } + val color1 = parseColor(service.getColor(0)) + val color2 = parseColor(service.getColor(1)) + Column( modifier = Modifier.fillMaxSize().padding(16.dp) ) { @@ -47,71 +66,195 @@ fun EconomyComparisonScreen() { Spacer(Modifier.height(16.dp)) Row(modifier = Modifier.fillMaxWidth()) { - Card( - modifier = Modifier.weight(1f), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Column(modifier = Modifier.padding(12.dp)) { - Text("Player 1", style = MaterialTheme.typography.titleSmall) - Text("Faction: $player1Faction", style = MaterialTheme.typography.bodySmall) - OutlinedTextField( - value = player1TownHalls, - onValueChange = { - player1TownHalls = it - it.toIntOrNull()?.let { count -> - comparisonService.changeNumberOfTownHalls(1, count) - } - }, - label = { Text("Town Halls") }, - modifier = Modifier.fillMaxWidth() - ) - } - } - + PlayerCard( + label = "Player 1", + faction = p1Faction, + townHallsText = p1TownHalls, + buildTimes = p1Times, + onTownHallsChange = { newValue -> + p1TownHalls = newValue + newValue.toIntOrNull()?.let { count -> + service.changeNumberOfTownHalls(0, count.coerceIn(0, 5)) + } + }, + onBuildTimeChange = { index, time -> + time.toIntOrNull()?.let { t -> + service.changeTownHallTiming(0, index, t) + } + }, + modifier = Modifier.weight(1f) + ) Spacer(Modifier.width(12.dp)) - - Card( - modifier = Modifier.weight(1f), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Column(modifier = Modifier.padding(12.dp)) { - Text("Player 2", style = MaterialTheme.typography.titleSmall) - Text("Faction: $player2Faction", style = MaterialTheme.typography.bodySmall) - OutlinedTextField( - value = player2TownHalls, - onValueChange = { - player2TownHalls = it - it.toIntOrNull()?.let { count -> - comparisonService.changeNumberOfTownHalls(2, count) - } - }, - label = { Text("Town Halls") }, - modifier = Modifier.fillMaxWidth() - ) - } - } + PlayerCard( + label = "Player 2", + faction = p2Faction, + townHallsText = p2TownHalls, + buildTimes = p2Times, + onTownHallsChange = { newValue -> + p2TownHalls = newValue + newValue.toIntOrNull()?.let { count -> + service.changeNumberOfTownHalls(1, count.coerceIn(0, 5)) + } + }, + onBuildTimeChange = { index, time -> + time.toIntOrNull()?.let { t -> + service.changeTownHallTiming(1, index, t) + } + }, + modifier = Modifier.weight(1f) + ) } + Spacer(Modifier.height(16.dp)) + HorizontalDivider() Spacer(Modifier.height(16.dp)) - Card( - modifier = Modifier.fillMaxWidth().weight(1f), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Column( - modifier = Modifier.fillMaxSize().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = "Comparison Chart (placeholder)", - style = MaterialTheme.typography.titleMedium - ) - Text( - text = "Economy comparison visualization will appear here.", - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 8.dp) + val eco1 = service.buildsToCompare[0].economyOverTimeModel + val eco2 = service.buildsToCompare[1].economyOverTimeModel + val lastInterval = minOf( + eco1.lastOrNull()?.interval ?: 0, + eco2.lastOrNull()?.interval ?: 0 + ) + val e1 = eco1.find { it.interval == lastInterval } + val e2 = eco2.find { it.interval == lastInterval } + + ComparisonChart( + alloy1 = e1?.alloy ?: 0f, + alloy2 = e2?.alloy ?: 0f, + ether1 = e1?.ether ?: 0f, + ether2 = e2?.ether ?: 0f, + workers1 = e1?.workerCount ?: 0, + workers2 = e2?.workerCount ?: 0, + color1 = color1, + color2 = color2, + interval = lastInterval, + modifier = Modifier.fillMaxWidth().weight(1f) + ) + } +} + +@Composable +private fun PlayerCard( + label: String, + faction: String, + townHallsText: String, + buildTimes: List, + onTownHallsChange: (String) -> Unit, + onBuildTimeChange: (Int, String) -> Unit, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text(label, style = MaterialTheme.typography.titleSmall) + Spacer(Modifier.height(4.dp)) + Text("Faction: $faction", style = MaterialTheme.typography.bodySmall) + Spacer(Modifier.height(8.dp)) + OutlinedTextField( + value = townHallsText, + onValueChange = onTownHallsChange, + label = { Text("Town Halls (0-5)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + buildTimes.forEachIndexed { index, time -> + Spacer(Modifier.height(4.dp)) + OutlinedTextField( + value = time.toString(), + onValueChange = { onBuildTimeChange(index, it) }, + label = { Text("TH ${index + 1} build time") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) } } } } + +@Composable +private fun ComparisonChart( + alloy1: Float, + alloy2: Float, + ether1: Float, + ether2: Float, + workers1: Int, + workers2: Int, + color1: Color, + color2: Color, + interval: Int, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = Modifier.fillMaxSize().padding(16.dp).verticalScroll(rememberScrollState()) + ) { + Text( + text = "Economy at Interval $interval", + style = MaterialTheme.typography.titleMedium + ) + Spacer(Modifier.height(12.dp)) + + Text("Alloy", style = MaterialTheme.typography.labelLarge) + Spacer(Modifier.height(4.dp)) + ResourceBar(value = alloy1, max = maxOf(alloy1, alloy2, 1f), color = color1, label = "P1") + Spacer(Modifier.height(2.dp)) + ResourceBar(value = alloy2, max = maxOf(alloy1, alloy2, 1f), color = color2, label = "P2") + Spacer(Modifier.height(8.dp)) + + Text("Ether", style = MaterialTheme.typography.labelLarge) + Spacer(Modifier.height(4.dp)) + ResourceBar(value = ether1, max = maxOf(ether1, ether2, 1f), color = color1, label = "P1") + Spacer(Modifier.height(2.dp)) + ResourceBar(value = ether2, max = maxOf(ether1, ether2, 1f), color = color2, label = "P2") + Spacer(Modifier.height(8.dp)) + + Text("Workers", style = MaterialTheme.typography.labelLarge) + Spacer(Modifier.height(4.dp)) + ResourceBar(value = workers1.toFloat(), max = maxOf(workers1, workers2, 1).toFloat(), color = color1, label = "P1") + Spacer(Modifier.height(2.dp)) + ResourceBar(value = workers2.toFloat(), max = maxOf(workers1, workers2, 1).toFloat(), color = color2, label = "P2") + } + } +} + +@Composable +private fun ResourceBar(value: Float, max: Float, color: Color, label: String) { + Row( + modifier = Modifier.fillMaxWidth().height(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("$label: ", style = MaterialTheme.typography.bodySmall) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(end = 4.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth(fraction = (value / max).coerceIn(0f, 1f)) + .fillMaxHeight() + .background(color.copy(alpha = 0.7f)) + ) + } + Text( + text = "%.1f".format(value), + style = MaterialTheme.typography.bodySmall + ) + } +} + +private fun parseColor(color: String): Color = when (color.lowercase()) { + "red" -> Color(0xFFE53935) + "green" -> Color(0xFF43A047) + "blue" -> Color(0xFF1E88E5) + "yellow" -> Color(0xFFFDD835) + "purple" -> Color(0xFF8E24AA) + "orange" -> Color(0xFFFB8C00) + else -> Color(0xFF757575) +} diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HarassCalculatorScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HarassCalculatorScreen.kt index 99ce18b..dba8b8c 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HarassCalculatorScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HarassCalculatorScreen.kt @@ -6,29 +6,75 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +private const val CostOfWorker = 50 +private const val AlloyMinedPerSecondByWorker = 1.0f +private const val TimeToProduceWorker = 20 + @Composable fun HarassCalculatorScreen() { - var damageInput by remember { mutableStateOf("") } - var healthInput by remember { mutableStateOf("") } + var workersLostText by remember { mutableStateOf("") } + var townHallsText by remember { mutableStateOf("") } + val travelTimes = remember { mutableStateListOf() } + + val workersLost = (workersLostText.toIntOrNull() ?: 0).coerceIn(0, 20) + val townHalls = (townHallsText.toIntOrNull() ?: 1).coerceIn(1, 6) + + if (travelTimes.size < townHalls) { + val toAdd = townHalls - travelTimes.size + repeat(toAdd) { travelTimes.add("0") } + } else if (travelTimes.size > townHalls) { + repeat(travelTimes.size - townHalls) { travelTimes.removeLastOrNull() } + } + + val replacementCost = workersLost * CostOfWorker + + val fullBatches = workersLost / townHalls + val remainderWorkers = workersLost % townHalls + + var miningLoss = 0.0f + for (batch in 1..fullBatches) { + miningLoss += townHalls * AlloyMinedPerSecondByWorker * TimeToProduceWorker * batch + } + if (remainderWorkers > 0) { + miningLoss += remainderWorkers * AlloyMinedPerSecondByWorker * TimeToProduceWorker * (fullBatches + 1) + } + + val travelTimeValues = travelTimes.map { it.toFloatOrNull() ?: 0f } + val sumTravelTimes = travelTimeValues.take(townHalls).sum() + val remainderSum = travelTimeValues.take(remainderWorkers).sum() + + val travelLoss = AlloyMinedPerSecondByWorker * (fullBatches * sumTravelTimes + remainderSum) + + val totalAlloyLost = replacementCost + miningLoss + travelLoss + val averageTravelTime = if (workersLost > 0) travelLoss / (AlloyMinedPerSecondByWorker * workersLost) else 0f Column( - modifier = Modifier.fillMaxSize().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.fillMaxSize().padding(16.dp).verticalScroll(rememberScrollState()) ) { + Text( + text = "Harass Calculator", + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(Modifier.height(12.dp)) + Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( @@ -36,7 +82,7 @@ fun HarassCalculatorScreen() { ) ) { Text( - text = "Warning: This calculator may be out of date. Verify values with the latest game patch.", + text = "Might be out of date - This calculation is from several years ago and might not reflect the current state of the game.", style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(16.dp) ) @@ -44,25 +90,110 @@ fun HarassCalculatorScreen() { Spacer(Modifier.height(16.dp)) - Text( - text = "Harass Calculator", - style = MaterialTheme.typography.headlineMedium - ) - Spacer(Modifier.height(12.dp)) - OutlinedTextField( - value = damageInput, - onValueChange = { damageInput = it }, - label = { Text("Damage per hit") }, - modifier = Modifier.fillMaxWidth() + value = workersLostText, + onValueChange = { workersLostText = it }, + label = { Text("Number of Workers Lost (1-20)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) + Spacer(Modifier.height(8.dp)) OutlinedTextField( - value = healthInput, - onValueChange = { healthInput = it }, - label = { Text("Target health") }, - modifier = Modifier.fillMaxWidth() + value = townHallsText, + onValueChange = { townHallsText = it }, + label = { Text("Number of Town Halls (1-6)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(12.dp)) + + Text( + text = "Travel Time per Town Hall (seconds)", + style = MaterialTheme.typography.labelLarge + ) + Spacer(Modifier.height(4.dp)) + for (i in 0 until townHalls) { + OutlinedTextField( + value = travelTimes.getOrElse(i) { "0" }, + onValueChange = { newValue -> + if (i < travelTimes.size) { + travelTimes[i] = newValue + } + }, + label = { Text("Travel Time TH ${i + 1}") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + if (i < townHalls - 1) { + Spacer(Modifier.height(4.dp)) + } + } + + Spacer(Modifier.height(16.dp)) + + HorizontalDivider() + + Spacer(Modifier.height(16.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "Total Alloy Lost", + style = MaterialTheme.typography.titleMedium + ) + Text( + text = "%.2f".format(totalAlloyLost), + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(12.dp)) + HorizontalDivider() + Spacer(Modifier.height(8.dp)) + Text( + text = "Worker Replacement Cost: $replacementCost", + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "Delayed Mining Cost: %.2f".format(miningLoss), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "Average Travel Time: %.2fs".format(averageTravelTime), + style = MaterialTheme.typography.bodyMedium + ) + } + } + + Spacer(Modifier.height(16.dp)) + + HorizontalDivider() + + Spacer(Modifier.height(16.dp)) + + Text( + text = "How It Works", + style = MaterialTheme.typography.titleMedium + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "This calculator estimates the total alloy opportunity cost when workers are killed. It combines the replacement cost of producing new workers with the mining output lost while those workers are being produced and traveling back to harvest points.", + style = MaterialTheme.typography.bodyMedium + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "Each town hall produces one worker at a time. All town halls produce simultaneously, so with multiple town halls the replacement time is reduced. Workers produced in later batches wait longer, incurring greater mining losses. Each worker also incurs a travel time loss based on their town hall's travel time.", + style = MaterialTheme.typography.bodyMedium + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "Total = (Workers \u00d7 50) + Mining Loss + Travel Loss", + style = MaterialTheme.typography.bodyMedium ) } } diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HomeScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HomeScreen.kt index c3f018f..ed00afe 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HomeScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/HomeScreen.kt @@ -10,11 +10,17 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.MenuBook import androidx.compose.material.icons.automirrored.filled.Notes +import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Calculate -import androidx.compose.material.icons.filled.Memory -import androidx.compose.material.icons.filled.MenuBook import androidx.compose.material.icons.filled.CompareArrows +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Map +import androidx.compose.material.icons.filled.Memory +import androidx.compose.material.icons.filled.Shield +import androidx.compose.material.icons.filled.Storage +import androidx.compose.material.icons.filled.TableChart import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -36,11 +42,17 @@ data class NavCardData( @Composable fun HomeScreen() { val navItems = listOf( - NavCardData("Database", Icons.Default.MenuBook), - NavCardData("Build Calculator", Icons.Default.Calculate), + NavCardData("Database", Icons.AutoMirrored.Filled.MenuBook), + NavCardData("Build Calculator", Icons.Filled.Calculate), NavCardData("Notes", Icons.AutoMirrored.Filled.Notes), - NavCardData("Memory Tester", Icons.Default.Memory), + NavCardData("Memory Tester", Icons.Filled.Memory), NavCardData("Economy Comparison", Icons.Default.CompareArrows), + NavCardData("Harass Calculator", Icons.Default.Bolt), + NavCardData("Data Tables", Icons.Default.TableChart), + NavCardData("Roadmap", Icons.Default.Map), + NavCardData("Storage", Icons.Default.Storage), + NavCardData("Permissions", Icons.Default.Shield), + NavCardData("About", Icons.Default.Info), ) Column( diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StorageScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StorageScreen.kt index 0504364..94fe8f0 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StorageScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StorageScreen.kt @@ -1,29 +1,266 @@ package ca.jonathanmccaffrey.igp.ui.screens import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import ca.jonathanmccaffrey.igp.di.ServiceLocator +@OptIn(ExperimentalMaterial3Api::class) @Composable fun StorageScreen() { + val storageService = ServiceLocator.storageService + + var plainView by remember { mutableStateOf((storageService.getValue("is_plain_view", false) as? Boolean) ?: false) } + var dynamicFormatting by remember { mutableStateOf((storageService.getValue("is_dynamic_formatting", false) as? Boolean) ?: false) } + + var attackTime by remember { mutableStateOf(((storageService.getValue("attack_time", 0) as? Number)?.toInt() ?: 0).toString()) } + var travelTime by remember { mutableStateOf(((storageService.getValue("travel_time", 0) as? Number)?.toInt() ?: 0).toString()) } + var buildInputDelay by remember { mutableStateOf(((storageService.getValue("build_input_delay", 0) as? Number)?.toInt() ?: 0).toString()) } + var waitTime by remember { mutableStateOf(((storageService.getValue("wait_time", 0) as? Number)?.toInt() ?: 0).toString()) } + var waitTo by remember { mutableStateOf(((storageService.getValue("wait_to", 0) as? Number)?.toInt() ?: 0).toString()) } + + var selectedFaction by remember { mutableStateOf((storageService.getValue("selected_faction", "") as? String) ?: "Aru") } + var selectedImmortal by remember { mutableStateOf((storageService.getValue("selected_immortal", "") as? String) ?: "Mala") } + var factionExpanded by remember { mutableStateOf(false) } + var immortalExpanded by remember { mutableStateOf(false) } + + val factionOptions = listOf("Aru", "Q'Rath") + val aruImmortals = listOf("Mala", "Xol", "Atzlan") + val qrathImmortals = listOf("Orzum", "Ajari") + val immortalOptions = if (selectedFaction == "Aru") aruImmortals else qrathImmortals + Column( - modifier = Modifier.fillMaxSize().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.fillMaxSize().padding(16.dp).verticalScroll(rememberScrollState()) ) { Text( - text = "Storage Management", + text = "Storage & Settings", style = MaterialTheme.typography.headlineMedium ) - Text( - text = "Manage your local storage data here.", - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(top = 16.dp) + + Spacer(Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Plain Entity View", + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f) + ) + Switch( + checked = plainView, + onCheckedChange = { + plainView = it + storageService.setValue("is_plain_view", it) + } + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Dynamic Formatting", + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f) + ) + Switch( + checked = dynamicFormatting, + onCheckedChange = { + dynamicFormatting = it + storageService.setValue("is_dynamic_formatting", it) + } + ) + } + + Spacer(Modifier.height(16.dp)) + HorizontalDivider() + Spacer(Modifier.height(16.dp)) + + OutlinedTextField( + value = attackTime, + onValueChange = { newValue -> + val filtered = newValue.filter { it.isDigit() } + attackTime = filtered + filtered.toIntOrNull()?.let { intVal -> + if (intVal in 0..2048) { + storageService.setValue("attack_time", intVal) + } + } + }, + label = { Text("Attack Time (0-2048)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = travelTime, + onValueChange = { newValue -> + val filtered = newValue.filter { it.isDigit() } + travelTime = filtered + filtered.toIntOrNull()?.let { intVal -> + if (intVal in 0..2048) { + storageService.setValue("travel_time", intVal) + } + } + }, + label = { Text("Travel Time (0-2048)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = buildInputDelay, + onValueChange = { newValue -> + val filtered = newValue.filter { it.isDigit() } + buildInputDelay = filtered + filtered.toIntOrNull()?.let { intVal -> + if (intVal in 0..600) { + storageService.setValue("build_input_delay", intVal) + } + } + }, + label = { Text("Building Input Delay (0-600)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(16.dp)) + HorizontalDivider() + Spacer(Modifier.height(16.dp)) + + ExposedDropdownMenuBox( + expanded = factionExpanded, + onExpandedChange = { factionExpanded = it } + ) { + OutlinedTextField( + value = selectedFaction, + onValueChange = {}, + readOnly = true, + label = { Text("Faction") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = factionExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = factionExpanded, + onDismissRequest = { factionExpanded = false } + ) { + factionOptions.forEach { faction -> + DropdownMenuItem( + text = { Text(faction) }, + onClick = { + selectedFaction = faction + factionExpanded = false + storageService.setValue("selected_faction", faction) + if (faction == "Aru" && selectedImmortal !in aruImmortals) { + selectedImmortal = "Mala" + storageService.setValue("selected_immortal", "Mala") + } else if (faction == "Q'Rath" && selectedImmortal !in qrathImmortals) { + selectedImmortal = "Orzum" + storageService.setValue("selected_immortal", "Orzum") + } + } + ) + } + } + } + + Spacer(Modifier.height(8.dp)) + + ExposedDropdownMenuBox( + expanded = immortalExpanded, + onExpandedChange = { immortalExpanded = it } + ) { + OutlinedTextField( + value = selectedImmortal, + onValueChange = {}, + readOnly = true, + label = { Text("Immortal") }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = immortalExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth() + ) + ExposedDropdownMenu( + expanded = immortalExpanded, + onDismissRequest = { immortalExpanded = false } + ) { + immortalOptions.forEach { immortal -> + DropdownMenuItem( + text = { Text(immortal) }, + onClick = { + selectedImmortal = immortal + immortalExpanded = false + storageService.setValue("selected_immortal", immortal) + } + ) + } + } + } + + Spacer(Modifier.height(16.dp)) + HorizontalDivider() + Spacer(Modifier.height(16.dp)) + + OutlinedTextField( + value = waitTime, + onValueChange = { newValue -> + val filtered = newValue.filter { it.isDigit() } + waitTime = filtered + filtered.toIntOrNull()?.let { intVal -> + if (intVal in 0..2048) { + storageService.setValue("wait_time", intVal) + } + } + }, + label = { Text("Wait Time (0-2048)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = waitTo, + onValueChange = { newValue -> + val filtered = newValue.filter { it.isDigit() } + waitTo = filtered + filtered.toIntOrNull()?.let { intVal -> + if (intVal in 0..2048) { + storageService.setValue("wait_to", intVal) + } + } + }, + label = { Text("Wait To (0-2048)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) } } diff --git a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StreamsScreen.kt b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StreamsScreen.kt index 3838c1a..3ced291 100644 --- a/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StreamsScreen.kt +++ b/app/src/main/java/ca/jonathanmccaffrey/igp/ui/screens/StreamsScreen.kt @@ -1,8 +1,13 @@ package ca.jonathanmccaffrey.igp.ui.screens import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,10 +25,23 @@ fun StreamsScreen() { text = "Streams", style = MaterialTheme.typography.headlineMedium ) - Text( - text = "Live streams and content from the Immortal community.", - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(top = 16.dp) - ) + Spacer(Modifier.height(16.dp)) + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "Coming Soon", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "Live streams and content from the Immortal: Gates of Pyre community will appear here.", + style = MaterialTheme.typography.bodyMedium + ) + } + } } -} +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 18318be..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.compose) apply false -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 34c5e9e..0000000 --- a/gradle.properties +++ /dev/null @@ -1,15 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. For more details, visit -# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file diff --git a/gradlew b/gradlew deleted file mode 100644 index ef07e01..0000000 --- a/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index db3a6ac..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/opencode.json b/opencode.json deleted file mode 100644 index e0959da..0000000 --- a/opencode.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "permission": "allow" -} diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 3939872..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -pluginManagement { - repositories { - google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - } - } - mavenCentral() - gradlePluginPortal() - } -} -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} - -rootProject.name = "IGP" -include(":app") - \ No newline at end of file