Initial Commit

This commit is contained in:
2026-05-29 14:17:46 -04:00
commit b7d0676d5b
498 changed files with 30308 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
*.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
+1
View File
@@ -0,0 +1 @@
/build
+53
View File
@@ -0,0 +1,53 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "ca.jonathanmccaffrey.igp"
compileSdk = 36
defaultConfig {
applicationId = "ca.jonathanmccaffrey.igp"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)
testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package ca.jonathanmccaffrey.igp
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("ca.jonathanmccaffrey.igp", appContext.packageName)
}
}
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.IGP">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.IGP">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,185 @@
package ca.jonathanmccaffrey.igp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Book
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Book
import androidx.compose.material.icons.outlined.Build
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
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.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import ca.jonathanmccaffrey.igp.di.ServiceLocator
import ca.jonathanmccaffrey.igp.ui.theme.IGPTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ServiceLocator.initialize(applicationContext)
enableEdgeToEdge()
setContent {
IGPTheme {
IGPApp()
}
}
}
}
data class BottomNavItem(
val label: String,
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector,
)
private val bottomNavItems = listOf(
BottomNavItem("Home", Icons.Filled.Home, Icons.Outlined.Home),
BottomNavItem("Database", Icons.Filled.Search, Icons.Outlined.Search),
BottomNavItem("Build Calculator", Icons.Filled.Build, Icons.Outlined.Build),
BottomNavItem("Notes", Icons.Filled.Book, Icons.Outlined.Book),
BottomNavItem("More", Icons.Filled.MoreVert, Icons.Outlined.MoreVert),
)
sealed class ScreenContent {
data object Home : ScreenContent()
data object Database : ScreenContent()
data object BuildCalculator : ScreenContent()
data object EconomyComparison : ScreenContent()
data object MemoryTester : ScreenContent()
data object Notes : ScreenContent()
data object About : ScreenContent()
data object HarassCalculator : ScreenContent()
data object DataTables : ScreenContent()
data object Permissions : ScreenContent()
data object DataCollection : ScreenContent()
data object Streams : ScreenContent()
data object Contact : ScreenContent()
data object RoadMap : ScreenContent()
}
@Composable
fun IGPApp() {
var selectedNavIndex by remember { mutableStateOf(0) }
val screens = listOf(
listOf(ScreenContent.Home),
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),
)
var currentScreen by remember { mutableStateOf<ScreenContent>(ScreenContent.Home) }
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
NavigationBar {
bottomNavItems.forEachIndexed { index, item ->
NavigationBarItem(
selected = selectedNavIndex == index,
onClick = {
selectedNavIndex = index
currentScreen = screens[index].first()
},
icon = {
Icon(
imageVector = if (selectedNavIndex == index) item.selectedIcon else item.unselectedIcon,
contentDescription = item.label,
)
},
label = { Text(item.label) },
)
}
}
},
) { 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))
}
}
}
@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)
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data
object Constants {
const val APP_NAME = "IGP Fan Reference"
const val CURRENT_PATCH = "1.0.0"
const val PATCH_DATE = "2026-05-29"
const val GITHUB_URL = "https://github.com/anomalyco/IGP"
}
@@ -0,0 +1,3 @@
package ca.jonathanmccaffrey.igp.data.models
class TravelTime(val index: Int, val value: Float)
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models
class Variable {
var key: String = ""
var value: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models
object Variables {
const val gamePatch = " June 2025 Build (playtest)"
}
@@ -0,0 +1,67 @@
package ca.jonathanmccaffrey.igp.data.models.buildorder
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class BuildOrderModel {
constructor() {
initialize(IdsEntity.FACTION_QRath)
}
constructor(factionType: String) {
initialize(factionType)
}
var name: String = ""
var notes: String = ""
var buildTypes: MutableList<String> = mutableListOf()
var currentSupplyUsed: Int = 0
var startedOrders: MutableMap<Int, MutableList<EntityModel>> = mutableMapOf()
var completedOrders: MutableMap<Int, MutableList<EntityModel>> = mutableMapOf()
var depletedOrders: MutableMap<Int, MutableList<EntityModel>> = mutableMapOf()
var uniqueCompletedTimes: MutableMap<String, Int> = mutableMapOf()
var uniqueCompletedCount: MutableMap<String, Int> = mutableMapOf()
var supplyCountTimes: MutableMap<Int, Int> = mutableMapOf()
var uniqueCompleted: MutableMap<String, MutableList<EntityModel>> = mutableMapOf()
var trainingCapacityUsed: MutableList<TrainingCapacityUsedModel> = mutableListOf()
fun initialize(faction: String) {
val factionStartingTownHall = when (faction) {
IdsEntity.FACTION_QRath -> IdsEntity.STARTING_TownHall_QRath
IdsEntity.FACTION_Aru -> IdsEntity.STARTING_TownHall_Aru
else -> ""
}
if (factionStartingTownHall.isEmpty()) throw Exception("Reminder to add support to new factions")
startedOrders = mutableMapOf(
0 to mutableListOf(
EntityModel.get(IdsEntity.STARTING_Bastion),
EntityModel.get(IdsEntity.STARTING_TownHall_Aru)
)
)
completedOrders = mutableMapOf(
0 to mutableListOf(
EntityModel.get(IdsEntity.STARTING_Bastion),
EntityModel.get(IdsEntity.STARTING_TownHall_Aru)
)
)
uniqueCompletedTimes = mutableMapOf(
IdsEntity.STARTING_Bastion to 0,
IdsEntity.STARTING_TownHall_Aru to 0
)
uniqueCompletedCount = mutableMapOf(
IdsEntity.STARTING_Bastion to 1,
IdsEntity.STARTING_TownHall_Aru to 1
)
supplyCountTimes = mutableMapOf(0 to 0)
}
fun getHarvestersCompletedBefore(interval: Int): List<EntityModel> {
return startedOrders.flatMap { (startTime, orders) ->
orders.filter { order ->
startTime + (order.production()?.buildTime ?: 0) <= interval &&
order.harvest() != null
}
}
}
}
@@ -0,0 +1,41 @@
package ca.jonathanmccaffrey.igp.data.models.buildorder
import ca.jonathanmccaffrey.igp.data.models.economy.EconomyModel
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityData
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class BuildToCompareModel {
private var numberOfTownHallExpansions: Int = 0
var faction: String = ""
val getTownHallEntity: EntityModel
get() = EntityData.get()[
if (faction == IdsEntity.FACTION_QRath) IdsEntity.BUILDING_Acropolis
else IdsEntity.BUILDING_GroveHeart
]!!
val getTownHallMining2Entity: EntityModel
get() = EntityData.get()[
if (faction == IdsEntity.FACTION_QRath) IdsEntity.BUPGRADE_MiningLevel2_QRath
else IdsEntity.BUPGRADE_MiningLevel2_Aru
]!!
var timeToBuildTownHall: MutableList<Int> = mutableListOf()
var economyOverTimeModel: MutableList<EconomyModel> = mutableListOf()
var buildOrderModel: BuildOrderModel = BuildOrderModel()
var chartColor: String = ""
fun getNumberOfTownHallExpansions(): Int = numberOfTownHallExpansions
fun setNumberOfTownHallExpansions(value: Int) {
if (value >= 0 && value < 6 && value != numberOfTownHallExpansions) {
numberOfTownHallExpansions = value
while (timeToBuildTownHall.size < numberOfTownHallExpansions)
timeToBuildTownHall.add((timeToBuildTownHall.size + 1) * 30)
while (timeToBuildTownHall.size > numberOfTownHallExpansions)
timeToBuildTownHall.removeAt(timeToBuildTownHall.lastIndex)
}
}
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.buildorder
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class ResearchSlot {
var startingInterval: Int = 30
var slots: Int = 1
var researchType: String = IdsEntity.BUILDING_Reliquary
}
@@ -0,0 +1,10 @@
package ca.jonathanmccaffrey.igp.data.models.buildorder
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class TrainingCapacityUsedModel {
var usedSlots: Int = 4
var usedBuilding: String = IdsEntity.BUILDING_LegionHall
var startingUsageTime: Int = 30
var stopUsageTime: Int = 60
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.buildorder
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class TrainingSlot {
var startingInterval: Int = 30
var slots: Int = 10
var productionType: String = IdsEntity.BUILDING_LegionHall
}
@@ -0,0 +1,37 @@
package ca.jonathanmccaffrey.igp.data.models.chart
import ca.jonathanmccaffrey.igp.data.models.chart.enums.ChartColourType
class ChartModel {
var points: MutableList<PointModel> = mutableListOf()
var chartColor: String = ChartColourType.Red.name
var offset: Int = 0
var intervalDisplayMax: Int = 300
var valueDisplayMax: Int = 5000
var highestIntervalPoint: Float = 5000f
var highestValuePoint: Float = 5000f
companion object {
fun getAll(): List<ChartModel> {
val cs = mutableListOf<ChartModel>()
val c1 = ChartModel().apply {
intervalDisplayMax = 1000
valueDisplayMax = 300
chartColor = "Orange"
points = mutableListOf()
}
for (i in 0 until c1.intervalDisplayMax) {
c1.points.add(PointModel().apply {
interval = i.toFloat()
value = (i.toFloat() / c1.intervalDisplayMax.toFloat() * c1.valueDisplayMax.toFloat()).toInt()
.toFloat()
})
}
cs.add(c1)
return cs
}
}
}
@@ -0,0 +1,17 @@
package ca.jonathanmccaffrey.igp.data.models.chart
class PointModel {
var interval: Float = 0f
var value: Float = 0f
var tempValue: Float = 0f
fun getInterval(highestInterval: Float, displayScale: Float): String {
val display = interval / highestInterval * displayScale
return display.toInt().toString()
}
fun getValue(highestValue: Float, displayScale: Float): String {
val display = value / highestValue * displayScale
return display.toInt().toString()
}
}
@@ -0,0 +1,11 @@
package ca.jonathanmccaffrey.igp.data.models.chart.enums
enum class ChartColourType {
Red,
LightGreen,
Cyan,
Yellow,
Orange,
LightBlue,
Pink
}
@@ -0,0 +1,18 @@
package ca.jonathanmccaffrey.igp.data.models.economy
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
class EconomyModel {
var interval: Int = 0
var alloy: Float = 0f
var alloyIncome: Float = 0f
var ether: Float = 0f
var etherIncome: Float = 0f
var pyre: Float = 0f
var supply: Int = 0
var workerCount: Int = 6
var busyWorkerCount: Int = 0
var creatingWorkerCount: Int = 0
var creatingWorkerDelays: MutableList<Int> = mutableListOf()
var harvestPoints: MutableList<EntityModel> = mutableListOf()
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.economy
class EconomyOverTimeModel {
var lastInterval: Int = 0
var economyOverTime: MutableList<EconomyModel> = mutableListOf()
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.economy.enums
enum class HarvesterType {
Worker,
EtherExtractor,
Bastion
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.economy.enums
enum class RequestType {
Unit,
Building
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.economy.enums
enum class WorkerStateType {
Building,
Unit
}
@@ -0,0 +1,206 @@
package ca.jonathanmccaffrey.igp.data.models.entity
import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityData
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.EntityHotkeyModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdAbilityModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdArmyModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdPassiveModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdPyreSpellModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdUpgradeModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityIdVanguardModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.IEntityPartInterface
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityInfoModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityMechanicModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityMovementModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityPassiveModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityProductionModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityRequirementModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityStrategyModel
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.parts.EntityVanguardAddedModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityVanguardReplacedModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityVitalityModel
import ca.jonathanmccaffrey.igp.data.models.entity.parts.EntityWeaponModel
import ca.jonathanmccaffrey.igp.data.models.entity.types.DescriptiveType
class EntityModel(
var dataType: String,
var entityType: String,
var isSpeculative: Boolean = false
) {
var entityParts: MutableList<IEntityPartInterface> = mutableListOf()
var descriptive: String = DescriptiveType.None
fun getName(): String {
val entityInfo = entityParts.firstOrNull { it is EntityInfoModel } as? EntityInfoModel
return entityInfo?.name ?: ""
}
fun getFaction(): String {
val entityInfo = entityParts.firstOrNull { it is EntityFactionModel } as? EntityFactionModel
return entityInfo?.let { EntityData.get()[it.faction]?.getName() } ?: ""
}
fun getImmortal(): String {
val entityInfo = entityParts.firstOrNull { it is EntityVanguardAddedModel } as? EntityVanguardAddedModel
return entityInfo?.let { EntityData.get()[it.immortalId]?.getName() } ?: ""
}
fun clone(): EntityModel {
val cloned = EntityModel(dataType, entityType, isSpeculative)
cloned.entityParts = entityParts.toMutableList()
cloned.descriptive = descriptive
return cloned
}
fun copyFrom(entity: EntityModel) {
dataType = entity.dataType
entityType = entity.entityType
entityParts = entity.entityParts.toMutableList()
}
fun addPart(unitPart: IEntityPartInterface): EntityModel {
unitPart.parent = this
entityParts.add(unitPart)
return this
}
fun info(): EntityInfoModel =
entityParts.find { it is EntityInfoModel } as EntityInfoModel
fun supply(): EntitySupplyModel? =
entityParts.find { it is EntitySupplyModel } as? EntitySupplyModel
fun tier(): EntityTierModel =
entityParts.find { it is EntityTierModel } as EntityTierModel
fun production(): EntityProductionModel? =
entityParts.find { it is EntityProductionModel } as? EntityProductionModel
fun movement(): EntityMovementModel =
entityParts.find { it is EntityMovementModel } as EntityMovementModel
fun vitality(): EntityVitalityModel =
entityParts.find { it is EntityVitalityModel } as EntityVitalityModel
fun requirements(): List<EntityRequirementModel> =
entityParts.filterIsInstance<EntityRequirementModel>()
fun weapons(): List<EntityWeaponModel> =
entityParts.filterIsInstance<EntityWeaponModel>()
fun replaceds(): List<EntityVanguardReplacedModel> =
entityParts.filterIsInstance<EntityVanguardReplacedModel>()
fun vanguardAdded(): EntityVanguardAddedModel? =
entityParts.find { it is EntityVanguardAddedModel } as? EntityVanguardAddedModel
fun hotkey(): EntityHotkeyModel? =
entityParts.find { it is EntityHotkeyModel } as? EntityHotkeyModel
fun faction(): EntityFactionModel? =
entityParts.find { it is EntityFactionModel } as? EntityFactionModel
fun harvest(): EntityHarvestModel? =
entityParts.find { it is EntityHarvestModel } as? EntityHarvestModel
fun idAbilities(): List<EntityIdAbilityModel> =
entityParts.filterIsInstance<EntityIdAbilityModel>()
fun idArmies(): List<EntityIdArmyModel> =
entityParts.filterIsInstance<EntityIdArmyModel>()
fun idPassives(): List<EntityIdPassiveModel> =
entityParts.filterIsInstance<EntityIdPassiveModel>()
fun idUpgrades(): List<EntityIdUpgradeModel> =
entityParts.filterIsInstance<EntityIdUpgradeModel>()
fun idVanguards(): List<EntityIdVanguardModel> =
entityParts.filterIsInstance<EntityIdVanguardModel>()
fun idPyreSpells(): List<EntityIdPyreSpellModel> =
entityParts.filterIsInstance<EntityIdPyreSpellModel>()
fun mechanics(): List<EntityMechanicModel> =
entityParts.filterIsInstance<EntityMechanicModel>()
fun passives(): List<EntityPassiveModel> =
entityParts.filterIsInstance<EntityPassiveModel>()
fun strategies(): List<EntityStrategyModel> =
entityParts.filterIsInstance<EntityStrategyModel>()
companion object {
private var _database: MutableMap<String, EntityModel>? = null
private var _entityModels: MutableList<EntityModel>? = null
private var _entityModelsOnlyHotkey: MutableList<EntityModel>? = null
private var _entityModelsByHotkey: MutableMap<String, MutableList<EntityModel>>? = null
fun getDictionary(): Map<String, EntityModel> {
if (_database == null) _database = EntityData.get().toMutableMap()
return _database!!
}
fun get(entity: String): EntityModel {
if (_database == null) _database = EntityData.get().toMutableMap()
return _database!![entity]!!
}
fun getList(): List<EntityModel> {
if (_entityModels == null) _entityModels = EntityData.get().values.toMutableList()
return _entityModels!!
}
fun getListOnlyHotkey(): List<EntityModel> {
if (_entityModelsOnlyHotkey == null) {
_entityModelsOnlyHotkey = mutableListOf()
for (entity in EntityData.get().values) {
if (entity.hotkey() != null) {
_entityModelsOnlyHotkey!!.add(entity)
}
}
}
return _entityModelsOnlyHotkey!!
}
fun getEntitiesByHotkey(): Map<String, List<EntityModel>> {
if (_entityModelsByHotkey == null) {
_entityModelsByHotkey = mutableMapOf()
for (entity in getList()) {
val entityHotkey = entity.hotkey()
if (entityHotkey != null) {
if (!_entityModelsByHotkey!!.containsKey(entityHotkey.hotkey))
_entityModelsByHotkey!![entityHotkey.hotkey] = mutableListOf()
_entityModelsByHotkey!![entityHotkey.hotkey]!!.add(entity)
}
}
}
return _entityModelsByHotkey!!
}
fun getFrom(
hotkey: String,
hotkeyGroup: String,
holdSpace: Boolean,
faction: String,
immortal: String
): EntityModel? {
if (hotkey.isEmpty()) return null
if (!getEntitiesByHotkey().containsKey(hotkey)) return null
val foundList = getEntitiesByHotkey()[hotkey]!!.filter { entity ->
entity.hotkey()?.hotkeyGroup == hotkeyGroup &&
entity.hotkey()?.holdSpace == holdSpace &&
entity.faction()?.faction == faction &&
(entity.vanguardAdded()?.immortalId == immortal || entity.vanguardAdded() == null) &&
(entity.replaceds().isEmpty() || entity.replaceds().none { it.immortalId == immortal })
}
return foundList.firstOrNull()
}
}
}
@@ -0,0 +1,50 @@
package ca.jonathanmccaffrey.igp.data.models.entity.data
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
object EntityData {
private fun getAbilityData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Ability.cs - contains abilities like RadiantWard, Windstep, etc.
return emptyMap()
}
private fun getArmyData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Army.cs - contains army units, vanguards, etc.
return emptyMap()
}
private fun getBuildingData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Building.cs - contains buildings and upgrades
return emptyMap()
}
private fun getImmortalData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Immortal.cs - contains immortal entities
return emptyMap()
}
private fun getMiscData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Misc.cs - contains misc entities like rocks, teapots
return emptyMap()
}
private fun getPassiveData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Passive.cs - contains passive entities
return emptyMap()
}
private fun getResearchData(): Map<String, EntityModel> {
// TODO: Convert from EntityData.Research.cs - contains research/upgrade entities
return emptyMap()
}
fun get(): Map<String, EntityModel> {
return getResearchData() +
getArmyData() +
getAbilityData() +
getMiscData() +
getImmortalData() +
getBuildingData() +
getPassiveData()
}
}
@@ -0,0 +1,26 @@
package ca.jonathanmccaffrey.igp.data.models.entity.data
object EntityType {
const val None = "None"
const val Any = "Any"
const val Teapot = "Teapot"
const val Family = "Family"
const val Faction = "Faction"
const val Command = "Command"
const val Worker = "Worker"
const val Army = "Army"
const val Building = "Building"
const val Building_Upgrade = "Building_Upgrade"
const val Immortal = "Immortal"
const val Pyre_Spell = "Spell"
const val Pyre_Event = "Pyre_Event"
const val Ability = "Ability"
const val Tech = "Tech"
const val Passive = "Passive"
}
object ImmortalSpellType {
const val Utitlity = "Utitlity"
const val Combat = "Combat"
const val Ultimate = "Ultimate"
}
@@ -0,0 +1,253 @@
package ca.jonathanmccaffrey.igp.data.models.entity.data
object IdsEntity {
const val Any = "Any"
const val None = "None"
const val PYREEVENT_TowerKilled = "9a923928-b016-49f2-8c7d-950abf09e287"
const val PYREEVENT_CampTaken = "cc27a9b2-69e2-4322-8102-7a9f8bea7871"
const val PYREEVENT_MinerTaken = "5b158cf2-2810-4a2a-8131-c4fe4b392ce9"
const val TEAPOT_Teapot = "7c30273e-b73b-458f-88d6-545360d046cc"
const val TEAPOT_FlyingTeapot = "3eb385ed-7086-4abb-8670-332e7a16c008"
const val FAMILY_Sylv = "73d69995-128d-4cfe-a40a-e5f00491f87b"
const val FAMILY_Human = "466d3be4-5322-4d5c-88d2-fb26563c8ee8"
const val FAMILY_Angelic = "c43686a6-f6d0-4a18-9870-338c64511051"
const val FAMILY_Coalition = "8aba0aff-ce18-4e15-afdc-28bb12a112bb"
const val FAMILY_Rae = "ce8d60f3-b590-4619-ad90-27e65f77312b"
const val FAMILY_Demonic = "f61a3630-9474-4ec3-bc71-997cacc52bc1"
const val FAMILY_NazRa = "56cc934f-57a9-442c-909a-25690f836679"
const val FACTION_Neutral = "95da29af-99da-45fe-80f6-4ae1cc0d0f47"
const val FACTION_Aru = "fb103962-7518-48df-b7d9-83906a009db8"
const val FACTION_Iratek = "dbc12bda-b4f2-4fa0-8270-18dc1646d62d"
const val FACTION_Yul = "9c0492af-1ef8-4113-9010-92178493f8b3"
const val FACTION_Jora = "692f534f-2bdc-4b27-90b4-d88bed3dd910"
const val FACTION_Telmetra = "1b1e2742-81c9-42d0-966c-1e43eb11e34a"
const val FACTION_Kjor = "1b1a354a-863f-440e-b4e7-acad41a6ece8"
const val FACTION_QRath = "3bb4bdf7-d7bf-45cb-b123-02f8ba52b848"
const val FACTION_YRiah = "c7537d12-a536-4ef5-99b9-d7ad704716fe"
const val FACTION_ArkShai = "ffe82eea-b8c6-4933-9230-cd019fd1c259"
const val FACTION_Herlesh = "e6c09f4a-ae7d-4bfd-b5d4-a63445d59498"
const val FACTION_Khardu = "a1764de6-8178-4775-819a-59d23677dd76"
const val COMMAND_Attack = "168486ff-581e-46da-a308-74f1d7d615fd"
const val COMMAND_StandGround = "cb9ee44f-7293-4a82-826c-a3829491006b"
const val MISC_Rocks = "030016ad-b972-4ad5-a601-41a99f8db305"
const val NEUTRAL_Tower = "40a5d5c7-42b8-48af-b71f-d75fedafb7b6"
const val NEUTRAL_PyreCamp = "185a9895-63da-402b-8d09-4c8609d52b04"
const val NEUTRAL_PyreMiner = "474727ee-0353-462f-9241-fb9031eb52a8"
const val STARTING_Bastion = "73d17ce2-8304-43dc-a0dd-d4a5666ea0b3"
const val STARTING_Tower = "3ca43196-db92-4beb-b5c5-65bdffbd32cf"
const val STARTING_TownHall_Aru = "f08e5320-8419-4259-b48d-e201b1f05ccf"
const val STARTING_TownHall_QRath = "150a4727-f831-48be-81fe-4dfd3588dfec"
const val IMMORTAL_Orzum = "IMMORTAL_Orzum"
const val IMMORTAL_Ajari = "IMMORTAL_Ajari"
const val IMMORTAL_Atzlan = "IMMORTAL_Atzlan"
const val IMMORTAL_Mala = "IMMORTAL_Mala"
const val IMMORTAL_Xol = "IMMORTAL_Xol"
const val ISPELL_SummonCitadel = "ISPELL_SummonCitadel"
const val ISPELL_RookOfIra = "ISPELL_RookOfIra"
const val ISPELL_EmpireUnbroken = "ISPELL_EmpireUnbroken"
const val ISPELL_PillarOfHeaven = "ISPELL_PillarOfHeaven"
const val ISPELL_HeavensAegis = "ISPELL_HeavensAegis"
const val ISPELL_DeliverFromEvil = "ISPELL_DeliverFromEvil"
const val ISPELL_Salvation = "ISPELL_Salvation"
const val ISPELL_SummonGroveGuardian = "ISPELL_SummonGroveGuardian"
const val ISPELL_ConstructBloodWell = "ISPELL_ConstructBloodWell"
const val ISPELL_RedHarvest = "ISPELL_RedHarvest"
const val ISPELL_ProphetsFavor = "ISPELL_ProphetsFavor"
const val ISPELL_RainOfBlood = "ISPELL_RainOfBlood"
const val ISPELL_ProphetOfTheHunt = "ISPELL_ProphetOfTheHunt"
const val ISPELL_HuntingGrounds = "ISPELL_HuntingGrounds"
const val ISPELL_TheGreatHunt = "ISPELL_TheGreatHunt"
const val ISPELL_ProphetOfTheRoots = "ISPELL_ProphetOfTheRoots"
const val ISPELL_WallOfRoots = "ISPELL_WallOfRoots"
const val ISPELL_SummonDeepWyrm = "ISPELL_SummonDeepWyrm"
const val ISPELL_SummonRootBud = "ISPELL_SummonRootBud"
const val IPASSIVE_MendingGrace = "IPASSIVE_MendingGrace"
const val IPASSIVE_OrdainedConquest = "IPASSIVE_OrdainedConquest"
const val IPASSIVE_MothersHunger = "IPASSIVE_MothersHunger"
const val IPASSIVE_StalkersSense = "IPASSIVE_StalkersSense"
const val IPASSIVE_GreenThumb = "IPASSIVE_GreenThumb"
const val BUILDING_Acropolis = "BUILDING_Acropolis"
const val BUILDING_ApostleOfBinding = "BUILDING_ApostleOfBinding"
const val BUILDING_GroveHeart = "BUILDING_GroveHeart"
const val BUILDING_EtherMaw = "BUILDING_EtherMaw"
const val BUPGRADE_MiningLevel2_QRath = "BUPGRADE_MiningLevel2_QRath"
const val BUPGRADE_MiningLevel2_Aru = "BUPGRADE_MiningLevel2_Aru"
const val CONVERSION_EtherSruge_QRath = "CONVERSION_EtherSruge_QRath"
const val CONVERSION_EtherSruge_Aru = "CONVERSION_EtherSruge_Aru"
const val BUPGRADE_GodHeart = "BUPGRADE_GodHeart"
const val BUPGRADE_Omnivore = "BUPGRADE_Omnivore"
const val BUILDING_LegionHall = "BUILDING_LegionHall"
const val BUILDING_SoulFoundry = "BUILDING_SoulFoundry"
const val BUILDING_Angelarium = "BUILDING_Angelarium"
const val BUILDING_AltarOfTheWorthy = "BUILDING_AltarOfTheWorthy"
const val BUILDING_AmberWomb = "BUILDING_AmberWomb"
const val BUILDING_BoneCanopy = "BUILDING_BoneCanopy"
const val BUILDING_Reliquary = "BUILDING_Reliquary"
const val BUILDING_MonasteryOfIzur = "BUILDING_MonasteryOfIzur"
const val BUILDING_HouseOfFadingSaints = "BUILDING_HouseOfFadingSaints"
const val BUILDING_EyeOfAros = "BUILDING_EyeOfAros"
const val BUILDING_BearerOfTheCrown = "BUILDING_BearerOfTheCrown"
const val BUILDING_RedVale = "BUILDING_RedVale"
const val BUILDING_DeepNest = "BUILDING_DeepNest"
const val BUILDING_MurderHollow = "BUILDING_MurderHollow"
const val BUILDING_Neurocyte = "BUILDING_Neurocyte"
const val BUILDING_RootCradle = "BUILDING_RootCradle"
const val DEFENSE_FireSinger = "DEFENSE_FireSinger"
const val BUILDING_KeeperOfTheHardenedFlames = "BUILDING_KeeperOfTheHardenedFlames"
const val DEFENSE_Aerovore = "DEFENSE_Aerovore"
const val UPGRADE_GreavesOfAhqar = "UPGRADE_GreavesOfAhqar"
const val UPGRADE_FortifiedIcons = "UPGRADE_FortifiedIcons"
const val UPGRADE_PsalmOfFire = "UPGRADE_PsalmOfFire"
const val UPGRADE_FaithCastBlades = "UPGRADE_FaithCastBlades"
const val UPGRADE_RelicOfTheWrathfulGaze = "UPGRADE_RelicOfTheWrathfulGaze"
const val UPGRADE_WindStep = "UPGRADE_WindStep"
const val UPGRADE_ZephyrRange = "UPGRADE_ZephyrRange"
const val UPGRADE_SiroccoScript = "UPGRADE_SiroccoScript"
const val UPGRADE_IconOfTheEnduringVigil = "UPGRADE_IconOfTheEnduringVigil"
const val UPGRADE_AbsolverHealthUpgrade = "UPGRADE_AbsolverHealthUpgrade"
const val UPGRADE_Awestrike = "UPGRADE_Awestrike"
const val UPGRADE_IconOfKhastEem = "UPGRADE_IconOfKhastEem"
const val UPGRADE_WingsOfTheKenLatir = "UPGRADE_WingsOfTheKenLatir"
const val UPGRADE_TitheBlades = "UPGRADE_TitheBlades"
const val UPGRADE_Offering = "UPGRADE_Offering"
const val UPGRADE_PursuitLigaments = "UPGRADE_PursuitLigaments"
const val UPGRADE_EthericFibers = "UPGRADE_EthericFibers"
const val UPGRADE_ObstructingSwarm = "UPGRADE_ObstructingSwarm"
const val UPGRADE_Hematoma = "UPGRADE_Hematoma"
const val UPGRADE_ResinantSpeed = "UPGRADE_ResinantSpeed"
const val UPGRADE_RootShepherdHidden = "UPGRADE_RootShepherdHidden"
const val UPGRADE_RootShepherdSpeed = "UPGRADE_RootShepherdSpeed"
const val UPGRADE_EoxBowstring = "UPGRADE_EoxBowstring"
const val UPGRADE_SporeBurst = "UPGRADE_SporeBurst"
const val UPGRADE_VitellineCysts = "UPGRADE_VitellineCysts"
const val UPGRADE_HyperAdrenoceptors = "UPGRADE_HyperAdrenoceptors"
const val UPGRADE_BloodPlague = "UPGRADE_BloodPlague"
const val UPGRADE_BirthingStorm = "UPGRADE_BirthingStorm"
const val UPGRADE_GodphageDamage = "UPGRADE_GodphageDamage"
const val UPGRADE_GENERIC_Attack1 = "UPGRADE_GENERIC_Attack1"
const val UPGRADE_GENERIC_Defense1 = "UPGRADE_GENERIC_Defense1"
const val UPGRADE_RadiantWard = "UPGRADE_RadiantWard"
const val UPGRADE_Ambush = "UPGRADE_Ambush"
const val PASSIVE_Detection = "434468fa-83b2-4fc9-a38c-1a3d00bcf055"
const val PASSIVE_WraithBowRange = "196dd8a6-2044-44e1-aac4-fbaa40552699"
const val PASSIVE_HiddenX = "7b819996-ffc0-4e07-9c11-c91c5f9d467b"
const val PASSIVE_Respite = "607c39f4-a957-4a7a-8fc6-a239f9e570ec"
const val PASSIVE_BastionPassives = "ea42b9cb-2456-4ed2-b490-fcfde12c6153"
const val PASSIVE_HallowedWarrior = "fea43ced-33f3-4531-af7d-740c1789fec1"
const val PASSIVE_GreavesOfAhqar = "3c408d75-7bee-4089-84c0-74620ac708b6"
const val PASSIVE_FortifiedIcons = "35f3f02f-e22e-44be-b2ea-82972c383308"
const val PASSIVE_HarvestAlloy = "84bacf5a-b106-455c-8cff-66c3998404f8"
const val PASSIVE_RelicOfTheWrathfulGaze = "ccebc0c9-cfd5-465a-8a5d-2495bd745a83"
const val PASSIVE_WingsOfTheKenLatir = "48909ff5-63db-4c99-b62f-d290e127e0bf"
const val PASSIVE_ExecutionRites = "8d017a17-320f-4c2a-b139-d2d83bf7ecd0"
const val PASSIVE_IconOfKhastEem = "7a211da6-4713-40f5-a171-1e3b6e02bf09"
const val PASSIVE_FaithCastBlades = "da4f8c9c-1b22-4b3f-94fc-11bc12cde02d"
const val PASSIVE_ThroneMovingShot = "699423ed-7410-4daf-8b07-9dc733a8bf55"
const val PASSIVE_SiroccoScript = "11c21afa-ff88-4e42-9f97-a1d1595b115c"
const val PASSIVE_HallowingRites = "9c8ae47b-954e-4a17-8f35-f128c9114b61"
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_Zeal = "62c4942b-5578-422d-8d4e-d1789f4efa68"
const val PASSIVE_HallowedGround = "bdb28984-246f-4642-84ab-9e83c02b3e2e"
const val PASSIVE_Rootway = "46768d4a-5047-4973-b5ca-995cda25ee8d"
const val PASSIVE_BehemothCapacity = "a210f109-d3ac-44d4-9724-601c795a2394"
const val PASSIVE_QuitlStorage2 = "0b27b863-fce5-40e4-96c7-6df94bdd92b9"
const val PASSIVE_Temporary = "940c04f1-df0b-4cf7-9514-09dfd9009554"
const val PASSIVE_Stalk = "9c107bfd-0050-4670-91b8-f9a8d771225d"
const val PASSIVE_Ambush = "9d0a9482-0303-4a15-bb88-972f6ae60a39"
const val PASSIVE_FallenHarvest = "d209e13f-d631-40d7-90e1-d9845b51b8d4"
const val PASSIVE_RestoreLifeblood = "fa213bc6-336c-4510-8a4b-db9ccfc54d62"
const val PASSIVE_Transfusion = "e67a3d6d-f2bb-4622-9e4d-ea2a26af2f39"
const val PASSIVE_HallowedRuin = "402b555a-eb8a-4065-bd25-465d190b30c7"
const val PASSIVE_ExternalDigestion = "2563723b-4a75-4a17-a104-1f5ac3b79a06"
const val PASSIVE_PursuitLigaments = "9f9676d5-cf6c-417d-9d08-ace400ccd39b"
const val PASSIVE_OssifyingSwarm = "b8897247-8393-416e-b246-409a6b3263c2"
const val PASSIVE_CastingFromBlood = "c97d1cf1-67d9-402b-9fa1-1abb9bfd7bfd"
const val PASSIVE_QuenchingScythes = "dbf07db4-e7b6-4f81-9f8e-e5391850eead"
const val PASSIVE_AaroxBurn = "921fe250-2b97-40c0-9765-9e6c1e766dd5"
const val PASSIVE_EngorgedArteries = "5b742d12-f695-4948-a00c-debdcb8b3717"
const val PASSIVE_ProjectileGestation = "e14f144f-8fa7-4cd5-bb9e-bed06e8af135"
const val PASSIVE_GuidingAmber = "9eab6701-0f0d-4858-b8a4-14e3a5dab822"
const val PASSIVE_GodstoneBulwark = "482189ac-713d-4870-a960-d2930961c486"
const val PASSIVE_Invervention = "3a70d237-1530-455a-b4f8-a626d708334c"
const val PASSIVE_BloodFrenzy = "356b6c33-a857-489c-8218-68c53d03db90"
const val PASSIVE_MendingDecree = "25d94c3d-dba9-4f02-abf4-904269b539c6"
const val PASSIVE_FireQuitl = "80f6b382-da1c-49a1-8235-1ea37983ea54"
const val PASSIVE_XacalDamage = "69928f20-5332-418f-ada3-694da3f7b199"
const val PASSIVE_XacalDefense = "PASSIVE_XacalDefense"
const val ABILITY_RadiantWard = "ABILITY_RadiantWard"
const val ABILITY_Windstep = "ABILITY_Windstep"
const val ABILITY_Intervention = "ABILITY_Intervention"
const val ABILITY_OrdainedPassage = "ABILITY_OrdainedPassage"
const val ABILITY_DeployMagi = "ABILITY_DeployMagi"
const val ABILITY_DeployAbsolver = "ABILITY_DeployAbsolver"
const val ABILITY_DeploySentinel = "ABILITY_DeploySentinel"
const val ABILITY_Smite = "ABILITY_Smite"
const val ABILITY_Awestrike = "ABILITY_Awestrike"
const val ABILITY_TitheBlades = "ABILITY_TitheBlades"
const val ABILITY_MobilizeQrath = "ABILITY_MobilizeQrath"
const val ABILITY_Offering = "ABILITY_Offering"
const val ABILITY_DiveBomb = "ABILITY_DiveBomb"
const val ABILITY_DrainingEmbrace = "ABILITY_DrainingEmbrace"
const val ABILITY_BloodPlague = "ABILITY_BloodPlague"
const val ABILITY_DeployResinant = "ABILITY_DeployResinant"
const val ABILITY_DeployBloodAnchor = "ABILITY_DeployBloodAnchor"
const val ABILITY_MobilizeAru = "ABILITY_MobilizeAru"
const val ABILITY_BloodyRebound = "ABILITY_BloodyRebound"
const val ABILITY_RootVice = "ABILITY_RootVice"
const val ABILITY_BirthingStorm = "ABILITY_BirthingStorm"
const val ABILITY_DeployDreadSister = "ABILITY_DeployDreadSister"
const val ABILITY_MorphToGodphage = "ABILITY_MorphToGodphage"
const val ABILITY_DeepTunnel = "ABILITY_DeepTunnel"
const val ABILITY_ObstructingSwarm = "ABILITY_ObstructingSwarm"
const val ABILITY_Hematoma = "ABILITY_Hematoma"
const val VANGUARD_Zentari_Orzum = "VANGUARD_Zentari_Orzum"
const val VANGUARD_Sceptre_Orzum = "VANGUARD_Sceptre_Orzum"
const val VANGUARD_ArkMother_Ajari = "VANGUARD_ArkMother_Ajari"
const val VANGUARD_Saoshin_Ajari = "VANGUARD_Saoshin_Ajari"
const val VANGUARD_RootShepard_Atzlan = "VANGUARD_RootShepard_Atzlan"
const val VANGUARD_Resinant_Atzlan = "VANGUARD_Resinant_Atzlan"
const val VANGUARD_BoneStalker_Xol = "VANGUARD_BoneStalker_Xol"
const val VANGUARD_WhiteWoodReaper_Xol = "VANGUARD_WhiteWoodReaper_Xol"
const val VANGUARD_DreadSister_Mala = "VANGUARD_DreadSister_Mala"
const val VANGUARD_Incubator_Mala = "VANGUARD_Incubator_Mala"
const val WORKER_Mote = "WORKER_Mote"
const val WORKER_Symbiote = "WORKER_Symbiote"
const val UNIT_Sipari = "UNIT_Sipari"
const val UNIT_Zephyr = "UNIT_Zephyr"
const val UNIT_Magi = "UNIT_Magi"
const val UNIT_Dervish = "UNIT_Dervish"
const val UNIT_Absolver = "UNIT_Absolver"
const val UNIT_Hallower = "UNIT_Hallower"
const val UNIT_Castigator = "UNIT_Castigator"
const val UNIT_Warden = "UNIT_Warden"
const val UNIT_Sentinel = "UNIT_Sentinel"
const val UNIT_Throne = "UNIT_Throne"
const val UNIT_SharU = "UNIT_SharU"
const val UNIT_MaskedHunter = "UNIT_MaskedHunter"
const val UNIT_Bloodbound = "UNIT_Bloodbound"
const val UNIT_BloodAnchor = "UNIT_BloodAnchor"
const val UNIT_Xacal = "UNIT_Xacal"
const val UNIT_RedSeer = "UNIT_RedSeer"
const val UNIT_Godphage = "cdc0398d-0173-4c8a-808c-053192712f05"
const val UNIT_Ichor = "ff4355f1-ac0c-4ea1-8a47-ef6791252bb9"
const val UNIT_WraithBow = "UNIT_WraithBow"
const val UNIT_Underspine = "UNIT_Underspine"
const val UNIT_Aarox = "UNIT_Aarox"
const val UNIT_Thrum = "UNIT_Thrum"
const val UNIT_Behemoth = "3783004b-65fd-4e4e-bef0-4cf161ea2d2d"
const val SUMMON_Quitl = "d554fb2a-eec5-45fd-bf36-a52d51e615e2"
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class EntityFactionModel : IEntityPartInterface() {
var faction: String = IdsEntity.FACTION_QRath
}
@@ -0,0 +1,18 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.ResourceType
class EntityHarvestModel : IEntityPartInterface() {
var resource: ResourceType = ResourceType.Alloy
var slots: Float = 0f
var harvestedPerInterval: Float = 0f
var harvestDelay: Float = 1f
var totalAmount: Int = 0
var requiresWorker: Boolean = false
fun isDepleted(interval: Float, startedAt: Float): Boolean {
val lifeTime = interval - startedAt
val totalHarvested = lifeTime * harvestedPerInterval
return totalHarvested > totalAmount
}
}
@@ -0,0 +1,29 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityHotkeyModel : IEntityPartInterface() {
var hotkey: String = ""
var holdSpace: Boolean = false
var hotkeyGroup: String = ""
fun isSelectedHotkey(keys: List<String>): Boolean {
return keys.contains(hotkey.uppercase())
}
fun isSelectedHotkeyGroup(keys: List<String>): Boolean {
return keys.contains(hotkeyGroup.uppercase())
}
fun isSelectedHoldSpace(keys: List<String>): Boolean {
return (keys.contains("SPACE") || keys.contains(" ")) == holdSpace
}
fun isSelectedHotkeyGroupWithSpace(keys: List<String>): Boolean {
var foundKey = false
var foundHold = false
for (key in keys) {
if (key.uppercase() == hotkeyGroup.uppercase()) foundKey = true
if (key.uppercase() == "SPACE" || key.uppercase() == " ") foundHold = true
}
return foundKey && foundHold == holdSpace
}
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdAbilityModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdArmyModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdPassiveModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdPyreSpellModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdUpgradeModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityIdVanguardModel : IEntityPartInterface() {
var id: String = ""
}
@@ -0,0 +1,11 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.DescriptiveType
class EntityInfoModel : IEntityPartInterface() {
var name: String = ""
var descriptive: String = DescriptiveType.None
var description: String = ""
var notes: String = ""
var flavorText: String = ""
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityMechanicModel : IEntityPartInterface() {
var name: String = ""
var description: String = ""
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.MovementType
class EntityMovementModel : IEntityPartInterface() {
var speed: Float = 0f
var movement: String = MovementType.Ground
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityPassiveModel : IEntityPartInterface() {
var name: String = ""
var description: String = ""
}
@@ -0,0 +1,14 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityProductionModel : IEntityPartInterface() {
var alloy: Int = 0
var ether: Int = 0
var pyre: Int = 0
var energy: Int = 0
var defensiveLayer: Int = 0
var buildTime: Int = 0
var cooldown: Float = 0f
var requiresWorker: Boolean = false
var consumesWorker: Boolean = false
var producedBy: String? = null
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityPyreRewardModel : IEntityPartInterface() {
var baseReward: Int = 0
var overTimeReward: Float = 0f
var overTimeRewardDuration: Int = 0
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.RequirementType
class EntityRequirementModel : IEntityPartInterface() {
var id: String = ""
var requirement: String = RequirementType.Production_Building
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityResearchCapacityModel : IEntityPartInterface() {
var slots: Int = 16
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityStrategyModel : IEntityPartInterface() {
var notes: String = ""
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntitySupplyModel : IEntityPartInterface() {
var takes: Int = 0
var grants: Int = 0
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityTierModel : IEntityPartInterface() {
var tier: Float = 0f
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
class EntityTrainingCapacityModel : IEntityPartInterface() {
var slots: Int = 16
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class EntityVanguardAddedModel : IEntityPartInterface() {
var immortalId: String = IdsEntity.IMMORTAL_Ajari
var replaceId: String = ""
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
class EntityVanguardReplacedModel : IEntityPartInterface() {
var immortalId: String = IdsEntity.IMMORTAL_Xol
var replacedById: String = ""
}
@@ -0,0 +1,16 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.ArmourType
import ca.jonathanmccaffrey.igp.data.models.entity.types.DefenseType
class EntityVitalityModel : IEntityPartInterface() {
var health: Int = 0
var defenseLayer: Int = 0
var lasts: Int = 0
var defense: String = DefenseType.None
var armor: String = ArmourType.Light
var isEtheric: Boolean = false
var isStructure: Boolean = false
var energy: Int = 0
var vision: Int = 0
}
@@ -0,0 +1,48 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.types.AttackType
import ca.jonathanmccaffrey.igp.data.models.entity.types.TargetType
class EntityWeaponModel : IEntityPartInterface() {
var id: Int = 1
var entityModelId: Int = 0
var range: Int = 40
var minimumRange: Int = 0
var attacksPerSecond: Float = 0f
var secondsBetweenAttacks: Float = 0f
var cooldown: Float = 0f
var charges: Float = 0f
var damage: Int = 0
var complexDamage: String = "deals 126 over 6 seconds"
var hasSplash: Boolean = false
var lightDamage: Int = 0
var mediumDamage: Int = 0
var heavyDamage: Int = 0
var structureDamageBonus: Int = 0
var ethericDamageBonus: Int = 0
var targets: String = TargetType.All
var attack: String = AttackType.DPS
fun damagePerSecond(): Float {
if (secondsBetweenAttacks == 0f) return damage * attacksPerSecond
return damage / secondsBetweenAttacks
}
fun damagePerSecondLight(): Float {
val dmg = if (lightDamage != 0) lightDamage else damage
if (secondsBetweenAttacks == 0f) return dmg * attacksPerSecond
return dmg / secondsBetweenAttacks
}
fun damagePerSecondMedium(): Float {
val dmg = if (mediumDamage != 0) mediumDamage else damage
if (secondsBetweenAttacks == 0f) return dmg * attacksPerSecond
return dmg / secondsBetweenAttacks
}
fun damagePerSecondHeavy(): Float {
val dmg = if (heavyDamage != 0) heavyDamage else damage
if (secondsBetweenAttacks == 0f) return dmg * attacksPerSecond
return dmg / secondsBetweenAttacks
}
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.parts
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
open class IEntityPartInterface {
var parent: EntityModel? = null
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object ArmourType {
const val Light = "Light"
const val Medium = "Medium"
const val Heavy = "Heavy"
const val Etheric = "Etheric"
const val Structure = "Structure"
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object AttackType {
const val DPS = "DPS"
const val Charges = "Charges"
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object BuildType {
const val Eco = "Eco"
const val Harass = "Harass"
const val Pyre_Hunting = "Pyre_Hunting"
const val Timing = "Timing"
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object DefenseType {
const val None = "None"
const val Shield = "Shield"
const val Overgrowth = "Overgrowth"
}
@@ -0,0 +1,26 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object DescriptiveType {
const val None = "None"
const val Frontliner = "Frontliner"
const val Support = "Support"
const val Generalist = "Generalist"
const val ZoneControl = "Zone Control"
const val AirKiller = "Air Killer"
const val Dislodger = "Dislodger"
const val EliteCaster = "Elite_Caster"
const val DamageCaster = "Damage Caster"
const val Worker = "Worker"
const val Skirmisher = "Skirmisher"
const val TownHall_Starting = "Town_Hall_Starting"
const val Stronghold = "Stronghold"
const val Economy = "Economy"
const val Training = "Training"
const val Technology = "Technology"
const val Defense = "Defense"
const val Summon = "Summon"
const val Upgrade = "Upgrade"
const val Ability = "Ability"
const val Passive = "Passive"
const val Applies_Debuff = "Applies_Debuff"
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object MovementType {
const val Ground = "Ground"
const val Hover = "Hover"
const val Air = "Air"
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object RequirementType {
const val Production_Building = "Production_Building"
const val Research_Building = "Research_Building"
const val Research_Upgrade = "Research_Upgrade"
const val Morph = "Morph"
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
enum class ResourceType {
Alloy,
Ether,
Pyre
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.entity.types
object TargetType {
const val Ground = "Ground"
const val Air = "Air"
const val All = "All"
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.feedback
object SeverityType {
const val Warning = "Warning"
const val Information = "Information"
const val Error = "Error"
const val Success = "Success"
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.feedback
class ToastModel {
var title: String = "addTitle"
var message: String = "addMessage"
var severityType: String = "addType"
var age: Float = 0f
}
@@ -0,0 +1,14 @@
package ca.jonathanmccaffrey.igp.data.models.hotkeys
enum class KeyType {
Action,
ControlGroup,
Army,
Train,
Research,
Construct,
Cancel,
Advance,
Economy,
Pyre
}
@@ -0,0 +1,95 @@
package ca.jonathanmccaffrey.igp.data.models.hotkeys
class HotkeyModel {
var keyText: String = ""
var keyType: KeyType = KeyType.Action
var positionX: Int = 0
var positionY: Int = 0
var isHidden: Boolean = false
var width: Int = 1
fun getColor(): String {
return when (keyType) {
KeyType.Action -> "#404146"
KeyType.Cancel -> "#621b1b"
KeyType.ControlGroup -> "#443512"
KeyType.Army -> "#443512"
KeyType.Train -> "#124443"
KeyType.Research -> "#221244"
KeyType.Construct -> "#122844"
KeyType.Pyre -> "#441212"
KeyType.Advance -> "#23262c"
KeyType.Economy -> "#262c23"
}
}
companion object {
val keyGroups: Array<String> = arrayOf("Z", "1", "2", "X", "CONTROL", "SHIFT", "C")
val hotKeys: Array<String> = arrayOf("`", "Q", "W", "E", "R", "A", "S", "F", "X", "V", "CAPSLOCK", "TAB")
fun getAll(): List<HotkeyModel> {
return listOf(
HotkeyModel().apply {
keyText = "Z"; keyType = KeyType.Train; positionX = 1; positionY = 3
},
HotkeyModel().apply {
keyText = "D"; keyType = KeyType.Army; positionX = 3; positionY = 2
},
HotkeyModel().apply {
keyText = "C"; keyType = KeyType.Construct; positionX = 3; positionY = 3
},
HotkeyModel().apply {
keyText = "V"; keyType = KeyType.Pyre; positionX = 4; positionY = 3
},
HotkeyModel().apply {
keyText = "X"; keyType = KeyType.Research; positionX = 2; positionY = 3
},
HotkeyModel().apply {
keyText = "`"; keyType = KeyType.Cancel; positionX = 0; positionY = 0
},
HotkeyModel().apply {
keyText = "TAB"; keyType = KeyType.Action; positionX = 0; positionY = 1
},
HotkeyModel().apply {
keyText = "Q"; keyType = KeyType.Action; positionX = 1; positionY = 1
},
HotkeyModel().apply {
keyText = "W"; keyType = KeyType.Action; positionX = 2; positionY = 1
},
HotkeyModel().apply {
keyText = "E"; keyType = KeyType.Action; positionX = 3; positionY = 1
},
HotkeyModel().apply {
keyText = "R"; keyType = KeyType.Action; positionX = 4; positionY = 1
},
HotkeyModel().apply {
keyText = "CAPSLOCK"; keyType = KeyType.Action; positionX = 0; positionY = 2
},
HotkeyModel().apply {
keyText = "A"; keyType = KeyType.Action; positionX = 1; positionY = 2
},
HotkeyModel().apply {
keyText = "S"; keyType = KeyType.Action; positionX = 2; positionY = 2
},
HotkeyModel().apply {
keyText = "D"; keyType = KeyType.ControlGroup; positionX = 3; positionY = 2
},
HotkeyModel().apply {
keyText = "F"; keyType = KeyType.Action; positionX = 4; positionY = 2
},
HotkeyModel().apply {
keyText = "SPACE"; keyType = KeyType.Advance; positionX = 1; positionY = 4; width = 4
},
HotkeyModel().apply {
keyText = "SHIFT"; keyType = KeyType.Economy; positionX = 0; positionY = 3
},
HotkeyModel().apply {
keyText = "ALT"; keyType = KeyType.Economy; positionX = 1; positionY = 5; isHidden = true
},
HotkeyModel().apply {
keyText = "CONTROL"; keyType = KeyType.Economy; positionX = 0; positionY = 4
}
)
}
}
}
@@ -0,0 +1,5 @@
package ca.jonathanmccaffrey.igp.data.models.hotkeys
enum class HotKeyType {
SPACE
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.memorytester
class AnswerEventArgs {
var name: String = ""
var isCorrect: Boolean = false
var guess: Int = 0
}
@@ -0,0 +1,15 @@
package ca.jonathanmccaffrey.igp.data.models.memorytester
class MemoryEntityModel {
var id: Int = 0
var name: String = ""
companion object {
val testData: List<MemoryEntityModel> = listOf(
MemoryEntityModel().apply { id = 1; name = "Masked Hunter" },
MemoryEntityModel().apply { id = 2; name = "Scepter" },
MemoryEntityModel().apply { id = 3; name = "Wraith Bow" },
MemoryEntityModel().apply { id = 4; name = "Thrum" }
)
}
}
@@ -0,0 +1,27 @@
package ca.jonathanmccaffrey.igp.data.models.memorytester
class MemoryQuestionModel {
var id: Int = 0
var memoryEntityModelId: Int = 0
var name: String = ""
var guess: Int = 0
var answer: Int = 0
var isRevealed: Boolean = false
companion object {
val testData: List<MemoryQuestionModel> = listOf(
MemoryQuestionModel().apply {
id = 1; memoryEntityModelId = 1; name = "Range"; answer = 600; isRevealed = false
},
MemoryQuestionModel().apply {
id = 2; memoryEntityModelId = 2; name = "Range"; answer = 600; isRevealed = false
},
MemoryQuestionModel().apply {
id = 3; memoryEntityModelId = 3; name = "Range"; answer = 600; isRevealed = false
},
MemoryQuestionModel().apply {
id = 4; memoryEntityModelId = 4; name = "Range"; answer = 600; isRevealed = false
}
)
}
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.notes
class NoteConnectionModel {
var id: Int = 1
var parentId: Int = 1
var childId: Int = 1
}
@@ -0,0 +1,47 @@
package ca.jonathanmccaffrey.igp.data.models.notes
import ca.jonathanmccaffrey.igp.data.models.website.SearchPointModel
class NoteContentModel {
var id: Int = 0
var parentId: Int? = null
var noteSectionModelId: Int? = null
var href: String = ""
var createdDate: String = ""
var updatedDate: String = ""
var name: String = ""
var description: String = ""
var content: String = ""
var loadedContent: String = ""
var isHidden: String = "False"
var isPreAlpha: String = "True"
var noteContentModels: MutableList<NoteContentModel> = mutableListOf()
var parent: NoteContentModel? = null
var pageOrder: Int = 0
fun getHeaders(): List<SearchPointModel> {
val regex = Regex("""^#* (.*)$""", RegexOption.MULTILINE)
val listOfMatches = regex.findAll(loadedContent)
return listOfMatches.map { capture ->
var cleanUp = capture.value.lowercase()
cleanUp = cleanUp.replace("#", "")
cleanUp = cleanUp.replace("\"", "")
cleanUp = cleanUp.trim()
cleanUp = cleanUp.replace(" ", "-")
SearchPointModel().apply {
title = capture.value.trim()
href = cleanUp
}
}.toList()
}
private fun getLink(): String {
val link = href
return if (parent != null) "${parent!!.getLink()}/$link" else link
}
fun getNoteLink(): String {
return "notes/${getLink()}"
}
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.notes
class NoteFrontMatterModel {
var title: String = ""
var summary: String = ""
var createdDate: String = ""
var updatedDate: String = ""
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.notes
class NoteSectionModel {
var id: Int = 0
var name: String = ""
var noteContentModels: MutableList<NoteContentModel> = mutableListOf()
}
@@ -0,0 +1,46 @@
package ca.jonathanmccaffrey.igp.data.models.repository
import ca.jonathanmccaffrey.igp.data.models.website.WebPageModel
object WebsiteData {
fun getPages(): List<WebPageModel> {
return listOf(
WebPageModel().apply {
id = 2
webSectionModelId = 2
name = "Build Calculator"
description = "Build order calculator for determining army timings"
href = "build-calculator"
isPrivate = "False"
icon = "fa-solid fa-helmet-battle"
},
WebPageModel().apply {
id = 1
webSectionModelId = 2
name = "Database"
description = "Database of game information"
href = "database"
isPrivate = "False"
icon = "fa-solid fa-clipboard-list"
},
WebPageModel().apply {
id = 3
webSectionModelId = 2
name = "Harass Calculator"
description = "Database of game information"
href = "harass-calculator"
isPrivate = "False"
icon = "fa-solid fa-bow-arrow"
},
WebPageModel().apply {
id = 4
webSectionModelId = 2
name = "Data Tables"
description = "Data tables"
href = "data-tables"
isPrivate = "False"
icon = "fa-solid fa-table-list"
}
)
}
}
@@ -0,0 +1,58 @@
package ca.jonathanmccaffrey.igp.data.models.roadmap
import ca.jonathanmccaffrey.igp.data.models.roadmap.enums.ReleasePriorityType
import ca.jonathanmccaffrey.igp.data.models.roadmap.enums.ReleaseStatusType
class ImmortalRoadMapModel {
var name: String = ""
var description: String = ""
var priority: String = ReleasePriorityType.High
var status: String = ReleaseStatusType.In_Development
companion object {
val data: List<ImmortalRoadMapModel> = listOf(
ImmortalRoadMapModel().apply {
name = "UI Overhaul"
description = "In the process of redoing the UI. Perhaps add 'Making Of' page for development related details, including a visual list of all components used, for ease of design reference. Ideally avoid menu bloat. Database, Build Calculator, Notes and Documentation, should be obvious main pages. Review 900px width on all pages."
priority = ReleasePriorityType.High
status = ReleaseStatusType.In_Development
},
ImmortalRoadMapModel().apply {
name = "Build Calculator Improvements"
description = "The Calculator will be optimized to perform faster. It needs to be updated to consider training queue limits. Also, it needs error popups added for not enough ether, not enough supply, or no more interval time."
priority = ReleasePriorityType.High
status = ReleaseStatusType.In_Development
},
ImmortalRoadMapModel().apply {
name = "Build Calculator Pyre"
description = "Build calculator should also handle pyre generation over time. 2 key will represent taking pyre camps. Make sure people can mark \"casted\" pyre spells as a part of the build order."
priority = ReleasePriorityType.Medium
status = ReleaseStatusType.Planned
},
ImmortalRoadMapModel().apply {
name = "Build Comparisons"
description = "You should be able to calculate two builds and load them against each other. Compare armies over time, to see when it would be best to strike against a certain build, and when it would be too late."
priority = ReleasePriorityType.Medium
status = ReleaseStatusType.Planned
},
ImmortalRoadMapModel().apply {
name = "Notes"
description = "There should be general notes on how to play Immortal. Nothing too extensive, but general faction and gameplay feel, like mentioning mechanics like Overgrowth for Aru and Wards for Q'Rath. Interesting but basic lore notes are also ideal, but all of these notes still need to be sortable in a method that feels natural, not a giant text bloat."
priority = ReleasePriorityType.High
status = ReleaseStatusType.In_Development
},
ImmortalRoadMapModel().apply {
name = "Documentation"
description = "There should be documents on how to use this website. Calculator, Database, etc. Ideally, these documents will be designed in a way that becomes easily maintainable with patches. (Currently, some QA document type stuff is appended to the bottom of each tool page.) Add a button to easily go from the current tool, to its matching documented."
priority = ReleasePriorityType.Medium
status = ReleaseStatusType.Planned
},
ImmortalRoadMapModel().apply {
name = "Test Automation"
description = "All patches should be tested via test automation, to avoid obvious bugs from getting into production. Add a informational test automation page to the Development section."
priority = ReleasePriorityType.Medium
status = ReleaseStatusType.Planned
}
)
}
}
@@ -0,0 +1,8 @@
package ca.jonathanmccaffrey.igp.data.models.roadmap.enums
object ReleasePriorityType {
const val High = "High"
const val Medium = "Medium"
const val Low = "Low"
const val Very_Low = "Very_Low"
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.roadmap.enums
object ReleaseStatusType {
const val In_Development = "In_Development"
const val Done = "Done"
const val Future_Possibility = "Future_Possibility"
const val Planned = "Planned"
const val Cancelled = "Cancelled"
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.website
class SearchPointModel {
var title: String = ""
var summary: String = ""
var tags: String = ""
var pointType: String = ""
var href: String = ""
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.website
object SupportedWebSizes {
const val Tablet = "479px"
const val Desktop = "1024px"
}
@@ -0,0 +1,20 @@
package ca.jonathanmccaffrey.igp.data.models.website
import ca.jonathanmccaffrey.igp.data.models.website.enums.WebDeploymentType
object WebDeploymentModel {
var deploymentType: WebDeploymentType = WebDeploymentType.Private
fun get(): List<String> {
return if (deploymentType == WebDeploymentType.Immortal) getImmortal() else emptyList()
}
private fun getImmortal(): List<String> {
return listOf(
"",
"build-calculator",
"comparison-charts",
"database"
)
}
}
@@ -0,0 +1,18 @@
package ca.jonathanmccaffrey.igp.data.models.website
import ca.jonathanmccaffrey.igp.data.models.website.enums.WebSectionType
class WebDescriptionModel {
var name: String = "Add Name"
var description: String = "Add description"
var parent: String = WebSectionType.None
var isPrivate: Boolean = true
companion object {
val list: MutableList<WebDescriptionModel> = mutableListOf()
fun getPages(forSection: String): List<WebDescriptionModel> {
return list.filter { it.parent == forSection }
}
}
}
@@ -0,0 +1,11 @@
package ca.jonathanmccaffrey.igp.data.models.website
class WebPageModel {
var id: Int = 0
var webSectionModelId: Int? = null
var name: String = "Add name"
var description: String = "Add description"
var href: String? = null
var isPrivate: String = "True"
var icon: String = ""
}
@@ -0,0 +1,12 @@
package ca.jonathanmccaffrey.igp.data.models.website
class WebSectionModel {
var id: Int = 0
var name: String = "Add name"
var description: String = "Add description"
var order: Int = 0
var isPrivate: String = "True"
var icon: String = "fa-icons"
var onlyIcon: Boolean = false
var webPageModels: MutableList<WebPageModel> = mutableListOf()
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.website.enums
enum class NavSelectionType {
None,
Section,
Page
}
@@ -0,0 +1,6 @@
package ca.jonathanmccaffrey.igp.data.models.website.enums
object NavigationStateType {
const val Default = "Default"
const val Hovering_Menu = "Hovering_Menu"
}
@@ -0,0 +1,7 @@
package ca.jonathanmccaffrey.igp.data.models.website.enums
enum class WebDeploymentType {
Private,
Public,
Immortal
}
@@ -0,0 +1,21 @@
package ca.jonathanmccaffrey.igp.data.models.website.enums
object WebPageType {
// TODO Deprecated
val None = "725d1adb-d5c8-4e51-bafb-09c86a94d0b0"
val IMMORTAL_About = "16e56a46-e593-4de5-a2ff-272b41a28d99"
val IMMORTAL_BuildCalculator = "c3abee8c-6b10-426d-8a7f-9042e536c007"
val IMMORTAL_MemoryTester = "1234"
val IMMORTAL_ChartComparision = "72ff145b-cfe0-454d-ae8f-ee977007eb73"
val IMMORTAL_Database = "2602b6ff-24c8-4924-ac59-3d7ad9dbca6b"
val IMMORTAL_HarassCalculator = "617547c2-c89a-4fa9-b063-5240d46d37b0"
val IMMORTAL_Notes = "47fbdc7e-97e7-4046-8c91-23f80b87fb86"
val IMMORTAL_KeyMapping = "315118a1-1d94-4237-a254-76de4ede845a"
val IMMORTAL_RoadMap = "119ddb93-ac4c-4481-9b89-91111201c543"
val IMMORTAL_Documentation = "f1a0a435-90ba-4cb3-8ae4-e27e1a249aa1"
val IMMORTAL_MakingOf = "532c08ed-c9ac-4425-b882-b5501127a567"
val IMMORTAL_Contact = "37dcbee4-53d9-4cbb-997c-445c2536dde8"
val IMMORTAL_ChangeLog = "36d459b4-4126-42f8-a391-222af8291c9c"
val IMMORTAL_Agile = "29a152d2-744a-4567-ba6a-68538c6462ae"
val IMMORTAL_Streams = "7f375283-83fa-44cf-8fe1-4b8cbd45007e"
}
@@ -0,0 +1,9 @@
package ca.jonathanmccaffrey.igp.data.models.website.enums
object WebSectionType {
val None = "28eefe79-3808-48da-8665-3eab5aebca1d"
val ImmortalGeneral = "1a7dce11-57ff-453e-abac-6eeab01b9a61"
val ImmortalTools = "c68afaa1-23b6-4ead-9622-797781bb4575"
val ImmortalResources = "3baddebc-e570-4855-946e-296b823411d6"
val ImmortalDevelopment = "df411a6f-2389-404b-b4fb-a86ebb764ecc"
}
@@ -0,0 +1,63 @@
package ca.jonathanmccaffrey.igp.di
import ca.jonathanmccaffrey.igp.services.immortal.BuildOrderService
import ca.jonathanmccaffrey.igp.services.immortal.EconomyComparisonService
import ca.jonathanmccaffrey.igp.services.immortal.EconomyService
import ca.jonathanmccaffrey.igp.services.immortal.EntityFilterService
import ca.jonathanmccaffrey.igp.services.immortal.EntityService
import ca.jonathanmccaffrey.igp.services.immortal.ImmortalSelectionService
import ca.jonathanmccaffrey.igp.services.immortal.KeyService
import ca.jonathanmccaffrey.igp.services.immortal.MemoryTesterService
import ca.jonathanmccaffrey.igp.services.immortal.TimingService
import ca.jonathanmccaffrey.igp.services.website.DataCollectionService
import ca.jonathanmccaffrey.igp.services.website.EntityDialogService
import ca.jonathanmccaffrey.igp.services.website.MyDialogService
import ca.jonathanmccaffrey.igp.services.website.NavigationService
import ca.jonathanmccaffrey.igp.services.website.PermissionService
import ca.jonathanmccaffrey.igp.services.website.SearchService
import ca.jonathanmccaffrey.igp.services.website.StorageService
import ca.jonathanmccaffrey.igp.services.website.ToastService
import ca.jonathanmccaffrey.igp.services.development.NoteService
import android.content.Context
object ServiceLocator {
fun initialize(context: Context) {
toastService = ToastService()
storageService = StorageService(context, toastService)
permissionService = PermissionService(storageService)
dataCollectionService = DataCollectionService(storageService)
navigationService = NavigationService()
entityDialogService = EntityDialogService()
myDialogService = MyDialogService()
entityFilterService = EntityFilterService()
entityService = EntityService()
timingService = TimingService(storageService)
buildOrderService = BuildOrderService(toastService, timingService)
economyService = EconomyService()
keyService = KeyService()
memoryTesterService = MemoryTesterService()
economyComparisonService = EconomyComparisonService()
noteService = NoteService(context)
searchService = SearchService(noteService)
immortalSelectionService = ImmortalSelectionService(storageService)
}
lateinit var toastService: ToastService
lateinit var searchService: SearchService
lateinit var permissionService: PermissionService
lateinit var storageService: StorageService
lateinit var dataCollectionService: DataCollectionService
lateinit var navigationService: NavigationService
lateinit var entityDialogService: EntityDialogService
lateinit var myDialogService: MyDialogService
lateinit var entityFilterService: EntityFilterService
lateinit var entityService: EntityService
lateinit var immortalSelectionService: ImmortalSelectionService
lateinit var buildOrderService: BuildOrderService
lateinit var economyService: EconomyService
lateinit var timingService: TimingService
lateinit var keyService: KeyService
lateinit var memoryTesterService: MemoryTesterService
lateinit var economyComparisonService: EconomyComparisonService
lateinit var noteService: NoteService
}
@@ -0,0 +1,252 @@
package ca.jonathanmccaffrey.igp.services
import ca.jonathanmccaffrey.igp.data.models.buildorder.BuildToCompareModel
import ca.jonathanmccaffrey.igp.data.models.economy.EconomyModel
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.feedback.ToastModel
import ca.jonathanmccaffrey.igp.data.models.memorytester.MemoryEntityModel
import ca.jonathanmccaffrey.igp.data.models.memorytester.MemoryQuestionModel
import ca.jonathanmccaffrey.igp.data.models.notes.NoteConnectionModel
import ca.jonathanmccaffrey.igp.data.models.notes.NoteContentModel
import ca.jonathanmccaffrey.igp.data.models.notes.NoteSectionModel
import ca.jonathanmccaffrey.igp.data.models.website.SearchPointModel
import ca.jonathanmccaffrey.igp.data.models.website.enums.NavSelectionType
import ca.jonathanmccaffrey.igp.services.immortal.MemoryTesterEvent
import ca.jonathanmccaffrey.igp.services.website.DialogContents
import ca.jonathanmccaffrey.igp.services.website.EntityFilterEvent
import kotlinx.coroutines.flow.StateFlow
typealias EntityFilterAction = (EntityFilterEvent) -> Unit
typealias MemoryAction = (MemoryTesterEvent) -> Unit
interface IToastService {
val onChange: StateFlow<Unit>
fun addToast(toast: ToastModel)
fun removeToast(toast: ToastModel)
fun hasToasts(): Boolean
fun getToasts(): List<ToastModel>
fun ageToasts()
fun clearAllToasts()
}
interface IDataCollectionService {
fun sendEvent(eventName: String)
}
interface IStorageService {
val onChange: StateFlow<Unit>
fun <T> getValue(key: String, default: T): T
fun <T> setValue(key: String, value: T)
suspend fun load()
}
interface IPermissionService {
val onChange: StateFlow<Unit>
fun getIsStorageEnabled(): Boolean
fun getIsDataCollectionEnabled(): Boolean
fun setIsStorageEnabled(isEnabled: Boolean)
fun setIsDataCollectionEnabled(isEnabled: Boolean)
}
interface ISearchService {
val onChange: StateFlow<Unit>
var searchPoints: MutableList<SearchPointModel>
val searches: MutableMap<String, MutableList<SearchPointModel>>
var isVisible: Boolean
fun search(entityId: String)
suspend fun load()
fun isLoaded(): Boolean
fun show()
fun hide()
}
interface IMyDialogService {
val onChange: StateFlow<Unit>
var isVisible: Boolean
fun show(dialogContents: DialogContents)
fun getDialogContents(): DialogContents
fun hide()
}
interface IEconomyComparisonService {
val onChange: StateFlow<Unit>
val buildsToCompare: MutableList<BuildToCompareModel>
fun changeNumberOfTownHalls(forPlayer: Int, toCount: Int)
fun changeTownHallTiming(forPlayer: Int, forTownHall: Int, toTiming: Int)
fun getTownHallCount(forPlayer: Int): Int
fun getTownHallBuildTime(forPlayer: Int, forTownHall: Int): Int
fun getTownHallBuildTimes(forPlayer: Int): List<Int>
fun changeFaction(forPlayer: Int, toFaction: String)
fun getFaction(forPlayer: Int): String
fun changeColor(forPlayer: Int, toColor: String)
fun getColor(forPlayer: Int): String
}
interface IEntityDialogService {
val onChange: StateFlow<Unit>
fun addDialog(entityId: String)
fun closeDialog()
fun backDialog()
fun getEntityId(): String?
fun hasDialog(): Boolean
fun hasHistory(): Boolean
}
interface INoteService {
val onChange: StateFlow<Unit>
var noteContentModels: MutableList<NoteContentModel>
var noteConnectionModels: MutableList<NoteConnectionModel>
var noteSectionModels: MutableList<NoteSectionModel>
fun update()
suspend fun load()
fun isLoaded(): Boolean
}
interface INavigationService {
val onChange: StateFlow<Unit>
fun changeNavigationSectionId(newState: Int)
fun getNavigationSectionId(): Int
fun changeNavigationState(newState: String)
fun getNavigationState(): String
fun back()
fun selectSection(webSectionType: Int)
fun selectPage(pageType: Int, webPageType: kotlin.reflect.KClass<*>)
fun getNavSelectionType(): NavSelectionType
fun getWebPageId(): Int
fun getWebSectionId(): Int
fun getRenderType(): kotlin.reflect.KClass<*>?
}
interface IBuildComparisonService {
val onChange: StateFlow<Unit>
fun setBuilds(buildToCompareModel: BuildToCompareModel)
fun get(): BuildToCompareModel
fun buildOrderAsYaml(): String
fun asJson(): String
fun loadJson(data: String): Boolean
}
interface ITimingService {
val onChange: StateFlow<Unit>
var buildingInputDelay: Int
var waitTime: Int
var waitTo: Int
fun getAttackTime(): Int
fun setAttackTime(timing: Int)
fun getTravelTime(): Int
fun setTravelTime(timing: Int)
}
interface IEconomyService {
val onChange: StateFlow<Unit>
fun getOverTime(): List<EconomyModel>
fun getEconomy(atInterval: Int): EconomyModel
suspend fun calculate(buildOrder: IBuildOrderService, timing: ITimingService, fromInterval: Int)
}
interface IEntityFilterService {
val onChange: StateFlow<EntityFilterEvent>
fun getFactionType(): String
fun getImmortalType(): String
fun getEntityType(): String
fun getSearchText(): String
fun getFactionChoices(): List<String>
fun getImmortalChoices(): List<String>
fun getEntityChoices(): List<String>
fun selectFactionType(factionType: String): Boolean
fun selectImmortalType(immortalType: String): Boolean
fun selectEntityType(entityType: String): Boolean
fun enterSearchText(searchText: String): Boolean
}
interface IEntityService {
fun getEntities(): List<EntityModel>
}
interface IEntityDisplayService {
val onChange: StateFlow<Unit>
fun defaultChoices(): List<String>
fun getDisplayType(): String
fun setDisplayType(displayType: String)
}
interface IImmortalSelectionService {
val onChange: StateFlow<Unit>
fun getFaction(): String
fun getImmortal(): String
fun selectFaction(faction: String): Boolean
fun selectImmortal(immortal: String): Boolean
}
interface IKeyService {
val onChange: StateFlow<Unit>
fun getAllPressedKeys(): List<String>
fun getHotkey(): String?
fun getHotkeyGroup(): String
fun isHoldingSpace(): Boolean
fun addPressedKey(key: String): Boolean
fun removePressedKey(key: String): Boolean
}
interface IMemoryTesterService {
val onChange: StateFlow<MemoryTesterEvent>
fun getEntities(): List<MemoryEntityModel>
fun getQuestions(): List<MemoryQuestionModel>
fun generateQuiz()
fun update(question: MemoryQuestionModel)
fun verify()
}
interface IBuildOrderService {
val onChange: StateFlow<Unit>
val startedOrders: Map<Int, List<EntityModel>>
val completedOrders: Map<Int, List<EntityModel>>
val depletedOrders: Map<Int, List<EntityModel>>
val uniqueCompletedTimes: Map<String, Int>
val supplyCountTimes: Map<Int, Int>
fun add(entity: EntityModel, withEconomy: IEconomyService): Boolean
fun add(entity: EntityModel, atInterval: Int)
fun addWait(forInterval: Int): Boolean
fun addWaitTo(interval: Int): Boolean
fun setName(name: String)
fun getName(): String
fun setNotes(notes: String)
fun getNotes(): String
fun deprecatedSetColor(color: String)
fun getColor(): String
fun willMeetRequirements(entity: EntityModel): Int?
fun willMeetSupply(entity: EntityModel): Int?
fun getOrders(): Map<Int, List<EntityModel>>
fun getCompletedBefore(interval: Int): List<EntityModel>
fun getUndepletedHarvestPointsCompletedBefore(interval: Int): List<EntityModel>
fun removeLast()
fun reset()
fun getLastRequestInterval(): Int
fun buildOrderAsYaml(): String
fun asJson(): String
}
@@ -0,0 +1,175 @@
package ca.jonathanmccaffrey.igp.services.development
import android.content.Context
import ca.jonathanmccaffrey.igp.data.models.notes.NoteConnectionModel
import ca.jonathanmccaffrey.igp.data.models.notes.NoteContentModel
import ca.jonathanmccaffrey.igp.data.models.notes.NoteSectionModel
import ca.jonathanmccaffrey.igp.services.INoteService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
class NoteService(
private val context: Context
) : INoteService {
private var isLoaded = false
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
override var noteContentModels: MutableList<NoteContentModel> = mutableListOf()
override var noteConnectionModels: MutableList<NoteConnectionModel> = mutableListOf()
override var noteSectionModels: MutableList<NoteSectionModel> = mutableListOf()
var noteContentModelsByPageOrder: MutableList<NoteContentModel> = mutableListOf()
override fun isLoaded(): Boolean = isLoaded
override suspend fun load() {
if (isLoaded) return
withContext(Dispatchers.IO) {
noteContentModels = loadNoteContents()
noteConnectionModels = loadNoteConnections()
noteSectionModels = loadNoteSections()
isLoaded = true
sortSQL()
withContext(Dispatchers.Main) {
_onChange.value = Unit
}
}
}
override fun update() {
_onChange.value = Unit
}
private fun contentById(id: Int): NoteContentModel? {
for (data in noteContentModels)
if (data.id == id) return data
return null
}
private fun sortSQL() {
for (connection in noteConnectionModels) {
contentById(connection.parentId)?.noteContentModels?.add(contentById(connection.childId)!!)
contentById(connection.childId)?.parent = contentById(connection.parentId)
}
for (content in noteContentModels) {
if (content.noteSectionModelId != null) {
for (section in noteSectionModels) {
if (section.id == content.noteSectionModelId) {
section.noteContentModels.add(content)
}
}
}
}
byPageOrder()
}
private fun byPageOrder() {
noteContentModelsByPageOrder = mutableListOf()
var order = 1
for (note in noteContentModels) {
if (note.parent != null) continue
note.pageOrder = order++
noteContentModelsByPageOrder.add(note)
fun getAllChildren(docs: NoteContentModel) {
for (doc in docs.noteContentModels) {
doc.pageOrder = order++
noteContentModelsByPageOrder.add(doc)
if (doc.noteContentModels.isNotEmpty()) getAllChildren(doc)
}
}
getAllChildren(note)
}
noteContentModelsByPageOrder = noteContentModelsByPageOrder
.sortedBy { it.pageOrder }
.toMutableList()
}
private fun readAssetText(fileName: String): String? {
return try {
context.assets.open(fileName).bufferedReader().use { it.readText() }
} catch (e: Exception) {
null
}
}
private fun loadNoteContents(): MutableList<NoteContentModel> {
val json = readAssetText("generated/NoteContentModels.json") ?: return mutableListOf()
return try {
val jsonArray = JSONArray(json)
val result = mutableListOf<NoteContentModel>()
for (i in 0 until jsonArray.length()) {
val obj = jsonArray.getJSONObject(i)
result.add(NoteContentModel().apply {
id = obj.optInt("id")
parentId = if (obj.isNull("parentId")) null else obj.optInt("parentId")
noteSectionModelId = if (obj.isNull("noteSectionModelId")) null else obj.optInt("noteSectionModelId")
href = obj.optString("href", "")
createdDate = obj.optString("createdDate", "")
updatedDate = obj.optString("updatedDate", "")
name = obj.optString("name", "")
description = obj.optString("description", "")
content = obj.optString("content", "")
loadedContent = obj.optString("loadedContent", "")
isHidden = obj.optString("isHidden", "False")
isPreAlpha = obj.optString("isPreAlpha", "True")
})
}
result
} catch (e: Exception) {
mutableListOf()
}
}
private fun loadNoteConnections(): MutableList<NoteConnectionModel> {
val json = readAssetText("generated/NoteConnectionModels.json") ?: return mutableListOf()
return try {
val jsonArray = JSONArray(json)
val result = mutableListOf<NoteConnectionModel>()
for (i in 0 until jsonArray.length()) {
val obj = jsonArray.getJSONObject(i)
result.add(NoteConnectionModel().apply {
id = obj.optInt("id", 1)
parentId = obj.optInt("parentId", 1)
childId = obj.optInt("childId", 1)
})
}
result
} catch (e: Exception) {
mutableListOf()
}
}
private fun loadNoteSections(): MutableList<NoteSectionModel> {
val json = readAssetText("generated/NoteSectionModels.json") ?: return mutableListOf()
return try {
val jsonArray = JSONArray(json)
val result = mutableListOf<NoteSectionModel>()
for (i in 0 until jsonArray.length()) {
val obj = jsonArray.getJSONObject(i)
result.add(NoteSectionModel().apply {
id = obj.optInt("id")
name = obj.optString("name", "")
})
}
result
} catch (e: Exception) {
mutableListOf()
}
}
}
@@ -0,0 +1,433 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.buildorder.BuildOrderModel
import ca.jonathanmccaffrey.igp.data.models.buildorder.TrainingCapacityUsedModel
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityType
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
import ca.jonathanmccaffrey.igp.data.models.entity.types.DescriptiveType
import ca.jonathanmccaffrey.igp.data.models.feedback.SeverityType
import ca.jonathanmccaffrey.igp.data.models.feedback.ToastModel
import ca.jonathanmccaffrey.igp.services.IBuildOrderService
import ca.jonathanmccaffrey.igp.services.IEconomyService
import ca.jonathanmccaffrey.igp.services.ITimingService
import ca.jonathanmccaffrey.igp.services.IToastService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.json.JSONObject
class BuildOrderService(
private val toastService: IToastService,
private val timingService: ITimingService
) : IBuildOrderService {
private val buildOrder = BuildOrderModel()
private var lastInterval: Int = 0
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
override val startedOrders: Map<Int, List<EntityModel>> get() = buildOrder.startedOrders
override val completedOrders: Map<Int, List<EntityModel>> get() = buildOrder.completedOrders
override val depletedOrders: Map<Int, List<EntityModel>> get() = buildOrder.depletedOrders
override val uniqueCompletedTimes: Map<String, Int> get() = buildOrder.uniqueCompletedTimes
override val supplyCountTimes: Map<Int, Int> get() = buildOrder.supplyCountTimes
init {
reset()
}
override fun getLastRequestInterval(): Int = lastInterval
override fun getOrders(): Map<Int, List<EntityModel>> = buildOrder.startedOrders
override fun add(entity: EntityModel, atInterval: Int) {
if (!buildOrder.startedOrders.containsKey(atInterval))
buildOrder.startedOrders[atInterval] = mutableListOf()
val production = entity.production()
val completedTime = atInterval + (production?.buildTime ?: 0)
if (!buildOrder.completedOrders.containsKey(completedTime))
buildOrder.completedOrders[completedTime] = mutableListOf()
buildOrder.startedOrders[atInterval]!!.add(entity.clone())
buildOrder.completedOrders[completedTime]!!.add(entity.clone())
buildOrder.uniqueCompletedTimes.putIfAbsent(entity.dataType, atInterval)
if (buildOrder.uniqueCompletedCount.putIfAbsent(entity.dataType, 1) != null)
buildOrder.uniqueCompletedCount[entity.dataType] = buildOrder.uniqueCompletedCount[entity.dataType]!! + 1
if (!buildOrder.uniqueCompleted.containsKey(entity.dataType))
buildOrder.uniqueCompleted[entity.dataType] = mutableListOf()
if (entity.production()?.producedBy != null)
buildOrder.trainingCapacityUsed.add(
TrainingCapacityUsedModel().apply {
startingUsageTime = atInterval
stopUsageTime = completedTime
usedSlots = entity.supply()?.takes ?: 1
usedBuilding = entity.production()!!.producedBy ?: ""
}
)
buildOrder.uniqueCompleted[entity.dataType]!!.add(entity)
if (entity.supply() != null && entity.supply()!!.takes > 0)
buildOrder.currentSupplyUsed += entity.supply()!!.takes
if (entity.supply() != null && entity.supply()!!.grants > 0)
buildOrder.supplyCountTimes[buildOrder.supplyCountTimes.keys.last() + entity.supply()!!.grants] = completedTime
if (atInterval > lastInterval) lastInterval = atInterval
_onChange.value = Unit
}
override fun addWait(forInterval: Int): Boolean {
if (forInterval < 0) {
toastService.addToast(
ToastModel().apply {
severityType = SeverityType.Error
title = "Wait"
message = "This should never happen."
}
)
return false
}
lastInterval += forInterval
if (!buildOrder.startedOrders.containsKey(lastInterval))
buildOrder.startedOrders[lastInterval] = mutableListOf()
if (!buildOrder.completedOrders.containsKey(lastInterval))
buildOrder.completedOrders[lastInterval] = mutableListOf()
_onChange.value = Unit
return true
}
override fun addWaitTo(interval: Int): Boolean {
if (interval <= lastInterval) {
toastService.addToast(
ToastModel().apply {
severityType = SeverityType.Error
title = "Logic Error"
message = "You cannot wait to a time that has already elapsed."
}
)
return false
}
lastInterval = interval
if (!buildOrder.startedOrders.containsKey(lastInterval))
buildOrder.startedOrders[lastInterval] = mutableListOf()
if (!buildOrder.completedOrders.containsKey(lastInterval))
buildOrder.completedOrders[lastInterval] = mutableListOf()
_onChange.value = Unit
return true
}
override fun willMeetRequirements(entity: EntityModel): Int? {
val requirements = entity.requirements()
if (requirements.isEmpty()) return 0
var metTime = 0
for (requiredEntity in requirements) {
val completedTime = buildOrder.uniqueCompletedTimes[requiredEntity.id]
if (completedTime != null) {
if (completedTime > metTime) metTime = completedTime
} else {
return null
}
}
return metTime
}
override fun willMeetSupply(entity: EntityModel): Int? {
val supply = entity.supply()
if (supply == null || supply.takes == 0) return 0
for ((supplyAt, time) in buildOrder.supplyCountTimes) {
if (supply.takes + buildOrder.currentSupplyUsed <= supplyAt)
return time
}
return null
}
override fun add(entity: EntityModel, withEconomy: IEconomyService): Boolean {
var atInterval = lastInterval
if (!handleSupply(entity, atInterval)) return false
val supplyAdjustedInterval = adjustForSupply(entity, atInterval) ?: return false
atInterval = supplyAdjustedInterval
val reqAdjustedInterval = adjustForRequirements(entity, atInterval) ?: return false
atInterval = reqAdjustedInterval
val economyAdjustedInterval = handleEconomy(entity, withEconomy, atInterval) ?: return false
atInterval = economyAdjustedInterval
val trainingAdjustedInterval = handleTrainingQueue(entity, atInterval) ?: return false
atInterval = trainingAdjustedInterval
add(entity, atInterval)
return true
}
override fun removeLast() {
if (buildOrder.startedOrders.keys.size <= 1) return
if (buildOrder.startedOrders.isEmpty()) {
buildOrder.startedOrders.remove(buildOrder.startedOrders.keys.last())
buildOrder.completedOrders.remove(buildOrder.completedOrders.keys.last())
lastInterval = buildOrder.startedOrders.keys.last()
return
}
val lastStarted = buildOrder.startedOrders.keys.last()
val lastCompleted = buildOrder.completedOrders.keys.last()
var entityRemoved: EntityModel? = null
if (buildOrder.startedOrders[lastStarted]!!.isNotEmpty()) {
entityRemoved = buildOrder.startedOrders[lastStarted]!!.removeLast()
buildOrder.completedOrders[lastCompleted]!!.removeLast()
}
if (buildOrder.startedOrders[lastStarted]!!.isEmpty())
buildOrder.startedOrders.remove(lastStarted)
if (buildOrder.completedOrders[lastCompleted]!!.isEmpty())
buildOrder.completedOrders.remove(lastCompleted)
lastInterval = if (buildOrder.startedOrders.keys.isNotEmpty())
buildOrder.startedOrders.keys.last()
else
0
if (entityRemoved != null) {
if (entityRemoved.supply()?.grants ?: 0 > 0)
buildOrder.supplyCountTimes.remove(buildOrder.supplyCountTimes.keys.last())
if (entityRemoved.supply()?.takes ?: 0 > 0)
buildOrder.currentSupplyUsed -= entityRemoved.supply()!!.takes
buildOrder.uniqueCompletedCount[entityRemoved.dataType] =
buildOrder.uniqueCompletedCount[entityRemoved.dataType]!! - 1
if (buildOrder.uniqueCompletedCount[entityRemoved.dataType] == 0)
buildOrder.uniqueCompletedTimes.remove(entityRemoved.dataType)
buildOrder.uniqueCompleted[entityRemoved.dataType]!!.removeLast()
if (entityRemoved.production() != null
&& entityRemoved.production()!!.producedBy != null
&& entityRemoved.supply() != null
&& entityRemoved.supply()!!.takes > 0
) {
buildOrder.trainingCapacityUsed.removeLast()
}
if (entityRemoved.info().descriptive == DescriptiveType.Worker) {
removeLast()
return
}
}
_onChange.value = Unit
}
override fun asJson(): String {
return JSONObject().toString(2)
}
override fun buildOrderAsYaml(): String {
return ""
}
override fun getCompletedBefore(interval: Int): List<EntityModel> {
val result = mutableListOf<EntityModel>()
for ((time, orders) in buildOrder.startedOrders) {
for (order in orders) {
val buildTime = order.production()?.buildTime ?: 0
if (time + buildTime <= interval)
result.add(order)
}
}
return result
}
override fun getUndepletedHarvestPointsCompletedBefore(interval: Int): List<EntityModel> {
val result = mutableListOf<EntityModel>()
for ((time, orders) in buildOrder.startedOrders) {
for (order in orders) {
val buildTime = order.production()?.buildTime ?: 0
val completedAt = time + buildTime
if (completedAt <= interval
&& order.harvest() != null
&& !order.harvest()!!.isDepleted(interval.toFloat(), completedAt.toFloat())
) {
result.add(order)
}
}
}
return result
}
override fun setName(name: String) {
buildOrder.name = name
_onChange.value = Unit
}
override fun getName(): String = buildOrder.name
override fun setNotes(notes: String) {
buildOrder.notes = notes
_onChange.value = Unit
}
override fun getNotes(): String = buildOrder.notes
override fun deprecatedSetColor(color: String) {
}
override fun getColor(): String = ""
override fun reset() {
lastInterval = 0
buildOrder.initialize(IdsEntity.FACTION_Aru)
_onChange.value = Unit
}
fun willMeetTrainingQueue(entity: EntityModel): Int? {
val supply = entity.supply()
val production = entity.production()
var checkedInterval = lastInterval
if (supply == null || production == null || supply.takes == 0) return 1
val producedBy = production.producedBy ?: return 1
val uniqueCompleted = buildOrder.uniqueCompleted[producedBy] ?: return null
var shortestIncrement = Int.MAX_VALUE
var didDelay = false
val trainingSlots = uniqueCompleted.sumOf { it.supply()?.grants ?: 0 }
while (true) {
var usedSlots = 0
for (used in buildOrder.trainingCapacityUsed) {
if (checkedInterval >= used.startingUsageTime && checkedInterval < used.stopUsageTime) {
usedSlots += used.usedSlots
val duration = used.stopUsageTime - used.startingUsageTime
if (duration < shortestIncrement) shortestIncrement = duration
}
}
if (usedSlots + supply.takes <= trainingSlots) {
if (didDelay)
toastService.addToast(
ToastModel().apply {
title = "Waited"
severityType = SeverityType.Information
message = "Had to wait ${checkedInterval - lastInterval}s for Training Queue."
}
)
return checkedInterval
}
checkedInterval += shortestIncrement
didDelay = true
if (shortestIncrement == Int.MAX_VALUE) return null
}
}
private fun handleEconomy(entity: EntityModel, withEconomy: IEconomyService, atInterval: Int): Int? {
val production = entity.production() ?: return atInterval
var adjustedInterval = atInterval
val economyOverTime = withEconomy.getOverTime()
for (interval in adjustedInterval until economyOverTime.size) {
val economyAtSecond = economyOverTime[interval]
if (economyAtSecond.alloy >= production.alloy
&& economyAtSecond.ether >= production.ether
&& economyAtSecond.pyre >= production.pyre
) {
adjustedInterval = interval
if (entity.entityType != EntityType.Army)
adjustedInterval += timingService.buildingInputDelay
return adjustedInterval
}
}
if (economyOverTime.isNotEmpty()) {
if (economyOverTime.last().ether < production.ether)
toastService.addToast(
ToastModel().apply {
title = "Not Enough Ether"
message = "Build more ether extractors!"
severityType = SeverityType.Error
}
)
if (economyOverTime.last().alloy < production.alloy)
toastService.addToast(
ToastModel().apply {
title = "Not Enough Alloy"
message = "Build more bases!"
severityType = SeverityType.Error
}
)
}
return null
}
private fun handleSupply(entity: EntityModel, atInterval: Int): Boolean {
val minSupplyInterval = willMeetSupply(entity)
if (minSupplyInterval == null) {
toastService.addToast(
ToastModel().apply {
title = "Supply Cap Reached"
message = "Build more supply!"
severityType = SeverityType.Error
}
)
return false
}
return true
}
private fun adjustForSupply(entity: EntityModel, atInterval: Int): Int? {
val minSupplyInterval = willMeetSupply(entity) ?: return null
return if (minSupplyInterval > atInterval) minSupplyInterval else atInterval
}
private fun adjustForRequirements(entity: EntityModel, atInterval: Int): Int? {
val minRequirementInterval = willMeetRequirements(entity) ?: return null
return if (minRequirementInterval > atInterval) minRequirementInterval else atInterval
}
private fun handleTrainingQueue(entity: EntityModel, atInterval: Int): Int? {
val minTrainingQueueInterval = willMeetTrainingQueue(entity)
if (minTrainingQueueInterval == null) {
toastService.addToast(
ToastModel().apply {
title = "Invalid"
message = "Invalid Training Queue error"
severityType = SeverityType.Error
}
)
return null
}
return if (minTrainingQueueInterval > atInterval) minTrainingQueueInterval else atInterval
}
}
@@ -0,0 +1,46 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.buildorder.BuildToCompareModel
import ca.jonathanmccaffrey.igp.services.IBuildComparisonService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.json.JSONObject
class DeprecatedBuildComparisonService : IBuildComparisonService {
private var buildToCompare = BuildToCompareModel()
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
override fun setBuilds(buildToCompareModel: BuildToCompareModel) {
buildToCompare = buildToCompareModel
_onChange.value = Unit
}
override fun get(): BuildToCompareModel = buildToCompare
override fun asJson(): String {
// Stub - would use kotlinx.serialization or Gson
return JSONObject().toString(2)
}
override fun loadJson(data: String): Boolean {
return try {
// Stub - would deserialize with kotlinx.serialization or Gson
hydratedLoadedJson()
_onChange.value = Unit
true
} catch (e: Exception) {
false
}
}
override fun buildOrderAsYaml(): String {
// Stub - YAML serialization not available on Android by default
return ""
}
private fun hydratedLoadedJson() {
}
}
@@ -0,0 +1,216 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.buildorder.BuildToCompareModel
import ca.jonathanmccaffrey.igp.data.models.economy.EconomyModel
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
import ca.jonathanmccaffrey.igp.data.models.entity.types.ResourceType
import ca.jonathanmccaffrey.igp.services.IEconomyComparisonService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.math.min
class EconomyComparisonService : IEconomyComparisonService {
private val intervalMax = 1024
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
override val buildsToCompare: MutableList<BuildToCompareModel> = mutableListOf(
BuildToCompareModel().apply {
setNumberOfTownHallExpansions(0)
faction = IdsEntity.FACTION_Aru
chartColor = "green"
},
BuildToCompareModel().apply {
setNumberOfTownHallExpansions(0)
faction = IdsEntity.FACTION_Aru
chartColor = "red"
}
)
init {
val e0 = calculateEconomy(buildsToCompare[0])
buildsToCompare[0].economyOverTimeModel = e0.toMutableList()
val e1 = calculateEconomy(buildsToCompare[1])
buildsToCompare[1].economyOverTimeModel = e1.toMutableList()
}
override fun changeNumberOfTownHalls(forPlayer: Int, toCount: Int) {
if (buildsToCompare[forPlayer].getNumberOfTownHallExpansions() == toCount) return
buildsToCompare[forPlayer].setNumberOfTownHallExpansions(toCount)
calculateBuildOrder(buildsToCompare[forPlayer])
_onChange.value = Unit
}
override fun changeTownHallTiming(forPlayer: Int, forTownHall: Int, toTiming: Int) {
if (buildsToCompare[forPlayer].timeToBuildTownHall[forTownHall] == toTiming) return
buildsToCompare[forPlayer].timeToBuildTownHall[forTownHall] = toTiming
calculateBuildOrder(buildsToCompare[forPlayer])
_onChange.value = Unit
}
override fun getTownHallCount(forPlayer: Int): Int =
buildsToCompare[forPlayer].getNumberOfTownHallExpansions()
override fun getTownHallBuildTime(forPlayer: Int, forTownHall: Int): Int =
buildsToCompare[forPlayer].timeToBuildTownHall[forTownHall]
override fun getTownHallBuildTimes(forPlayer: Int): List<Int> =
buildsToCompare[forPlayer].timeToBuildTownHall
override fun changeFaction(forPlayer: Int, toFaction: String) {
if (buildsToCompare[forPlayer].faction == toFaction) return
buildsToCompare[forPlayer].faction = toFaction
_onChange.value = Unit
}
override fun getFaction(forPlayer: Int): String =
buildsToCompare[forPlayer].faction
override fun changeColor(forPlayer: Int, toColor: String) {
if (buildsToCompare[forPlayer].chartColor == toColor) return
buildsToCompare[forPlayer].chartColor = toColor
_onChange.value = Unit
}
override fun getColor(forPlayer: Int): String =
buildsToCompare[forPlayer].chartColor
private fun calculateBuildOrder(buildToCompare: BuildToCompareModel) {
for (time in buildToCompare.timeToBuildTownHall) {
val townHall = buildToCompare.getTownHallEntity
val townHallMining2 = buildToCompare.getTownHallMining2Entity
addToBuildOrder(townHall, buildToCompare, time)
addToBuildOrder(townHallMining2, buildToCompare, time + townHall.production()!!.buildTime)
}
val economy = calculateEconomy(buildToCompare)
buildToCompare.economyOverTimeModel = economy.toMutableList()
}
private fun addToBuildOrder(entityModel: EntityModel, buildToCompare: BuildToCompareModel, atInterval: Int) {
val buildOrder = buildToCompare.buildOrderModel
if (!buildOrder.startedOrders.containsKey(atInterval))
buildOrder.startedOrders[atInterval] = mutableListOf()
val production = entityModel.production()
val completedTime = atInterval + (production?.buildTime ?: 0)
if (!buildOrder.completedOrders.containsKey(completedTime))
buildOrder.completedOrders[completedTime] = mutableListOf()
buildOrder.startedOrders[atInterval]!!.add(entityModel.clone())
buildOrder.completedOrders[completedTime]!!.add(entityModel.clone())
_onChange.value = Unit
}
private fun calculateEconomy(buildToCompare: BuildToCompareModel, fromInterval: Int = 0): List<EconomyModel> {
var from = fromInterval
if (from == 0) from = 1
val buildOrder = buildToCompare.buildOrderModel
val buildEconomyOverTime = buildToCompare.economyOverTimeModel.toMutableList()
while (buildEconomyOverTime.size < intervalMax) {
buildEconomyOverTime.add(EconomyModel().apply { interval = buildEconomyOverTime.size - 1 })
}
for (interval in from until intervalMax) {
buildEconomyOverTime[interval] = EconomyModel()
val economyAtSecond = buildEconomyOverTime[interval]
if (interval > 0) {
economyAtSecond.alloy = buildEconomyOverTime[interval - 1].alloy
economyAtSecond.ether = buildEconomyOverTime[interval - 1].ether
economyAtSecond.pyre = buildEconomyOverTime[interval - 1].pyre
economyAtSecond.workerCount = buildEconomyOverTime[interval - 1].workerCount
economyAtSecond.busyWorkerCount = buildEconomyOverTime[interval - 1].busyWorkerCount
economyAtSecond.creatingWorkerCount = buildEconomyOverTime[interval - 1].creatingWorkerCount
economyAtSecond.harvestPoints = buildEconomyOverTime[interval - 1].harvestPoints.toMutableList()
economyAtSecond.creatingWorkerDelays = buildEconomyOverTime[interval - 1].creatingWorkerDelays.toMutableList()
}
economyAtSecond.interval = interval
var freeWorkers = (economyAtSecond.workerCount - economyAtSecond.busyWorkerCount).toFloat()
var workersNeeded = 0
economyAtSecond.harvestPoints = buildOrder.getHarvestersCompletedBefore(interval).toMutableList()
economyAtSecond.pyre += 1
for (entity in economyAtSecond.harvestPoints) {
val harvester = entity.harvest() ?: continue
if (harvester.requiresWorker) {
if (harvester.resource == ResourceType.Alloy) {
val usedWorkers = min(harvester.slots, freeWorkers.toFloat())
economyAtSecond.alloy += harvester.harvestedPerInterval * usedWorkers
freeWorkers -= usedWorkers
if (usedWorkers < harvester.slots) workersNeeded += 1
}
} else {
when (harvester.resource) {
ResourceType.Ether -> economyAtSecond.ether += harvester.harvestedPerInterval * harvester.slots
ResourceType.Alloy -> economyAtSecond.alloy += harvester.harvestedPerInterval * harvester.slots
else -> {}
}
}
}
if (economyAtSecond.creatingWorkerCount > 0) {
var i = 0
while (i < economyAtSecond.creatingWorkerDelays.size) {
if (economyAtSecond.creatingWorkerDelays[i] > 0) {
if (economyAtSecond.alloy > 2.5f) {
economyAtSecond.alloy -= 2.5f
economyAtSecond.creatingWorkerDelays[i]--
}
} else {
economyAtSecond.creatingWorkerCount -= 1
economyAtSecond.workerCount += 1
economyAtSecond.creatingWorkerDelays.removeAt(i)
i--
}
i++
}
}
if (workersNeeded > economyAtSecond.creatingWorkerCount) {
economyAtSecond.creatingWorkerCount += 1
economyAtSecond.creatingWorkerDelays.add(50)
}
if (buildOrder.startedOrders.containsKey(interval)) {
for (order in buildOrder.startedOrders[interval]!!) {
val foundEntity = EntityModel.getDictionary()[order.dataType]!!
val production = foundEntity.production()
if (production != null) {
economyAtSecond.alloy -= production.alloy
economyAtSecond.ether -= production.ether
economyAtSecond.pyre -= production.pyre
if (production.requiresWorker) economyAtSecond.busyWorkerCount += 1
if (production.consumesWorker) economyAtSecond.workerCount -= 1
}
}
}
if (buildOrder.completedOrders.containsKey(interval)) {
for (newEntity in buildOrder.completedOrders[interval]!!) {
economyAtSecond.harvestPoints.add(newEntity)
val production = newEntity.production()
if (production != null && production.requiresWorker) economyAtSecond.busyWorkerCount -= 1
}
}
}
return buildEconomyOverTime
}
}
@@ -0,0 +1,191 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.economy.EconomyModel
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.data.models.entity.types.ResourceType
import ca.jonathanmccaffrey.igp.services.IBuildOrderService
import ca.jonathanmccaffrey.igp.services.IEconomyService
import ca.jonathanmccaffrey.igp.services.ITimingService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.math.min
class EconomyService : IEconomyService {
private var buildEconomyOverTime: MutableList<EconomyModel>? = null
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
override fun getOverTime(): List<EconomyModel> = buildEconomyOverTime ?: emptyList()
override suspend fun calculate(buildOrder: IBuildOrderService, timing: ITimingService, fromInterval: Int) {
var from = fromInterval
if (from == 0) from = 1
if (buildEconomyOverTime == null) {
buildEconomyOverTime = mutableListOf()
for (interval in 0 until timing.getAttackTime()) {
buildEconomyOverTime!!.add(EconomyModel().apply { this.interval = interval })
}
}
if (buildEconomyOverTime!!.size > timing.getAttackTime()) {
buildEconomyOverTime = buildEconomyOverTime!!.subList(0, timing.getAttackTime()).toMutableList()
}
while (buildEconomyOverTime!!.size < timing.getAttackTime()) {
buildEconomyOverTime!!.add(EconomyModel().apply { interval = buildEconomyOverTime!!.size - 1 })
}
for (interval in from until timing.getAttackTime()) {
buildEconomyOverTime!![interval] = EconomyModel()
val economyAtSecond = buildEconomyOverTime!![interval]
carryOverEconomyFromPreviousInterval(interval, economyAtSecond)
setupCurrentInterval(buildOrder, economyAtSecond, interval)
addPassivePyreGain(interval, economyAtSecond)
val freeWorkers = economyAtSecond.workerCount - economyAtSecond.busyWorkerCount.toFloat()
val workersNeeded = addFundsFromHarvestPoints(economyAtSecond, freeWorkers)
var remainingWorkersNeeded = workersNeeded
remainingWorkersNeeded -= calculateCreatingWorkerCosts(economyAtSecond)
makeNeededNewWorkersRequests(remainingWorkersNeeded, economyAtSecond)
subtractFundsOnRequestedOrders(buildOrder, interval, economyAtSecond)
handleAddingNewHarvestPointsAndWorkersToEconomy(buildOrder, interval, economyAtSecond)
}
_onChange.value = Unit
}
override fun getEconomy(atInterval: Int): EconomyModel {
val list = buildEconomyOverTime ?: return EconomyModel()
return if (atInterval >= list.size) list.last() else list[atInterval]
}
private fun setupCurrentInterval(
buildOrder: IBuildOrderService,
economyAtSecond: EconomyModel,
interval: Int
) {
economyAtSecond.interval = interval
economyAtSecond.harvestPoints = buildOrder
.getUndepletedHarvestPointsCompletedBefore(interval + 1)
.toMutableList()
}
private fun handleAddingNewHarvestPointsAndWorkersToEconomy(
buildOrder: IBuildOrderService,
interval: Int,
economyAtSecond: EconomyModel
) {
val completedAtInterval = buildOrder.completedOrders[interval] ?: return
for (newEntity in completedAtInterval) {
val entity = newEntity.clone()
economyAtSecond.harvestPoints.add(entity)
val production = newEntity.production()
if (production != null && production.requiresWorker) economyAtSecond.busyWorkerCount -= 1
}
}
private fun subtractFundsOnRequestedOrders(
buildOrder: IBuildOrderService,
interval: Int,
economyAtSecond: EconomyModel
) {
val ordersAtTime = buildOrder.startedOrders[interval] ?: return
for (order in ordersAtTime) {
val foundEntity = EntityModel.getDictionary()[order.dataType] ?: continue
val production = foundEntity.production() ?: continue
economyAtSecond.alloy -= production.alloy
economyAtSecond.ether -= production.ether
economyAtSecond.pyre -= production.pyre
if (production.requiresWorker) economyAtSecond.busyWorkerCount += 1
if (production.consumesWorker) economyAtSecond.workerCount -= 1
}
}
private fun makeNeededNewWorkersRequests(workersNeeded: Int, economyAtSecond: EconomyModel) {
if (workersNeeded <= economyAtSecond.creatingWorkerCount) return
economyAtSecond.creatingWorkerCount += 1
economyAtSecond.creatingWorkerDelays.add(20)
}
private fun calculateCreatingWorkerCosts(economyAtSecond: EconomyModel): Int {
var createdWorkers = 0
if (economyAtSecond.creatingWorkerCount <= 0) return createdWorkers
var i = 0
while (i < economyAtSecond.creatingWorkerDelays.size) {
if (economyAtSecond.creatingWorkerDelays[i] > 0) {
if (economyAtSecond.alloy > 2.5f) {
economyAtSecond.alloy -= 2.5f
economyAtSecond.creatingWorkerDelays[i]--
}
} else {
economyAtSecond.creatingWorkerCount -= 1
economyAtSecond.workerCount += 1
createdWorkers++
economyAtSecond.creatingWorkerDelays.removeAt(i)
i--
}
i++
}
return createdWorkers
}
private fun addFundsFromHarvestPoints(economyAtSecond: EconomyModel, freeWorkers: Float): Int {
var workersNeeded = 0
var remainingFreeWorkers = freeWorkers
for (entity in economyAtSecond.harvestPoints) {
val harvesterPoint = entity.harvest() ?: continue
if (harvesterPoint.requiresWorker) {
if (harvesterPoint.resource == ResourceType.Alloy) {
val usedWorkers = min(harvesterPoint.slots, remainingFreeWorkers)
economyAtSecond.alloy += harvesterPoint.harvestedPerInterval * usedWorkers
economyAtSecond.alloyIncome += harvesterPoint.harvestedPerInterval * usedWorkers
remainingFreeWorkers -= usedWorkers
if (usedWorkers < harvesterPoint.slots) workersNeeded += 1
}
} else {
when (harvesterPoint.resource) {
ResourceType.Ether -> {
economyAtSecond.ether += harvesterPoint.harvestedPerInterval * harvesterPoint.slots
economyAtSecond.etherIncome += harvesterPoint.harvestedPerInterval * harvesterPoint.slots
}
ResourceType.Alloy -> {
economyAtSecond.alloy += harvesterPoint.harvestedPerInterval * harvesterPoint.slots
economyAtSecond.alloyIncome += harvesterPoint.harvestedPerInterval * harvesterPoint.slots
}
ResourceType.Pyre -> { }
}
}
}
return workersNeeded
}
private fun addPassivePyreGain(interval: Int, economyAtSecond: EconomyModel) {
if (interval % 3 == 0) economyAtSecond.pyre += 1
}
private fun carryOverEconomyFromPreviousInterval(interval: Int, economyAtSecond: EconomyModel) {
if (interval <= 0) return
val previous = buildEconomyOverTime!![interval - 1]
economyAtSecond.alloy = previous.alloy
economyAtSecond.ether = previous.ether
economyAtSecond.pyre = previous.pyre
economyAtSecond.workerCount = previous.workerCount
economyAtSecond.busyWorkerCount = previous.busyWorkerCount
economyAtSecond.creatingWorkerCount = previous.creatingWorkerCount
economyAtSecond.harvestPoints = previous.harvestPoints.toMutableList()
economyAtSecond.creatingWorkerDelays = previous.creatingWorkerDelays.toMutableList()
}
}
@@ -0,0 +1,39 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.services.IEntityDisplayService
import ca.jonathanmccaffrey.igp.services.IStorageService
import ca.jonathanmccaffrey.igp.services.website.StorageKeys
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
object EntityViewType {
const val Detailed = "Detailed"
const val Plain = "Plain"
}
class EntityDisplayService(
storageService: IStorageService
) : IEntityDisplayService {
private var displayType: String
private val _onChange = MutableStateFlow(Unit)
override val onChange: StateFlow<Unit> = _onChange
init {
displayType = if (storageService.getValue(StorageKeys.IsPlainView, false)) {
EntityViewType.Plain
} else {
EntityViewType.Detailed
}
}
override fun defaultChoices(): List<String> = listOf(EntityViewType.Detailed, EntityViewType.Plain)
override fun getDisplayType(): String = displayType
override fun setDisplayType(displayType: String) {
this.displayType = displayType
_onChange.value = Unit
}
}
@@ -0,0 +1,123 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.entity.data.EntityType
import ca.jonathanmccaffrey.igp.data.models.entity.data.IdsEntity
import ca.jonathanmccaffrey.igp.services.IEntityFilterService
import ca.jonathanmccaffrey.igp.services.website.EntityFilterEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class EntityFilterService : IEntityFilterService {
private val entityChoices = mutableListOf<String>()
private val factionChoices = mutableListOf(
IdsEntity.Any, IdsEntity.FACTION_QRath, IdsEntity.FACTION_Aru
)
private val immortalChoices = mutableListOf<String>()
private var entityType: String = EntityType.Army
private var searchText = ""
private var selectedFaction: String = IdsEntity.Any
private var selectedImmortal: String = IdsEntity.Any
private val _onChange = MutableStateFlow(EntityFilterEvent.OnRefreshFaction)
override val onChange: StateFlow<EntityFilterEvent> = _onChange
init {
refreshImmortalChoices()
refreshEntityChoices()
}
override fun getEntityType(): String = entityType
override fun getFactionType(): String = selectedFaction
override fun getImmortalType(): String = selectedImmortal
override fun selectFactionType(factionType: String): Boolean {
if (selectedFaction == factionType) {
selectedFaction = IdsEntity.None
selectedImmortal = IdsEntity.None
refreshImmortalChoices()
refreshEntityChoices()
_onChange.value = EntityFilterEvent.OnRefreshFaction
return true
}
selectedFaction = factionType
selectedImmortal = IdsEntity.Any
refreshImmortalChoices()
refreshEntityChoices()
_onChange.value = EntityFilterEvent.OnRefreshFaction
return true
}
override fun selectImmortalType(immortalType: String): Boolean {
if (selectedImmortal == immortalType) {
selectedImmortal = IdsEntity.None
_onChange.value = EntityFilterEvent.OnRefreshImmortal
return true
}
selectedImmortal = immortalType
_onChange.value = EntityFilterEvent.OnRefreshImmortal
return true
}
override fun selectEntityType(entityType: String): Boolean {
if (this.entityType == entityType) return false
this.entityType = entityType
_onChange.value = EntityFilterEvent.OnRefreshEntity
return true
}
override fun enterSearchText(searchText: String): Boolean {
if (this.searchText == searchText) return false
this.searchText = searchText
_onChange.value = EntityFilterEvent.OnRefreshSearch
return true
}
override fun getFactionChoices(): List<String> = factionChoices
override fun getImmortalChoices(): List<String> = immortalChoices
override fun getEntityChoices(): List<String> = entityChoices
override fun getSearchText(): String = searchText
private fun refreshImmortalChoices() {
immortalChoices.clear()
if (selectedFaction == IdsEntity.FACTION_QRath || selectedFaction == IdsEntity.Any) {
immortalChoices.add(IdsEntity.IMMORTAL_Orzum)
immortalChoices.add(IdsEntity.IMMORTAL_Ajari)
}
if (selectedFaction == IdsEntity.FACTION_Aru || selectedFaction == IdsEntity.Any) {
immortalChoices.add(IdsEntity.IMMORTAL_Atzlan)
immortalChoices.add(IdsEntity.IMMORTAL_Mala)
immortalChoices.add(IdsEntity.IMMORTAL_Xol)
}
}
private fun refreshEntityChoices() {
entityChoices.clear()
if (selectedFaction == IdsEntity.FACTION_QRath || selectedFaction == IdsEntity.FACTION_Aru ||
selectedFaction == IdsEntity.Any
) {
entityChoices.add(EntityType.Army)
entityChoices.add(EntityType.Immortal)
entityChoices.add(EntityType.Passive)
entityChoices.add(EntityType.Building)
entityChoices.add(EntityType.Tech)
entityChoices.add(EntityType.Ability)
entityChoices.add(EntityType.Pyre_Spell)
entityChoices.add(EntityType.Worker)
}
if (selectedFaction == IdsEntity.Any) entityChoices.add(EntityType.Any)
}
}
@@ -0,0 +1,10 @@
package ca.jonathanmccaffrey.igp.services.immortal
import ca.jonathanmccaffrey.igp.data.models.entity.EntityModel
import ca.jonathanmccaffrey.igp.services.IEntityService
class EntityService : IEntityService {
override fun getEntities(): List<EntityModel> {
throw NotImplementedError("EntityService not implemented")
}
}

Some files were not shown because too many files have changed in this diff Show More