Refactor project data to allow server handling.

This commit is contained in:
Thomas Andres Gomez 2025-02-22 12:54:19 +01:00
parent 3c8eecdab5
commit 1e5f0d88ae
58 changed files with 742 additions and 469 deletions

View file

@ -8,6 +8,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.koin.core)
implementation(libs.kotlinx.serialization.json)
}
}

View file

@ -1,3 +0,0 @@
package com.pixelized.server.lwa
const val SERVER_PORT = 16030

View file

@ -1,5 +0,0 @@
package com.pixelized.server.lwa.protocol
enum class MessageType {
Roll
}

View file

@ -0,0 +1,3 @@
package com.pixelized.shared.lwa
const val SERVER_PORT = 16030

View file

@ -0,0 +1,34 @@
package com.pixelized.shared.lwa
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
import kotlinx.serialization.json.Json
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
val sharedModuleDependencies
get() = listOf(
toolsDependencies,
factoryDependencies,
useCaseDependencies,
)
val toolsDependencies
get() = module {
factory {
Json {
explicitNulls = false
prettyPrint = true
}
}
}
val factoryDependencies
get() = module {
factoryOf(::CharacterSheetJsonFactory)
}
val useCaseDependencies
get() = module {
factoryOf(::CharacterSheetUseCase)
}

View file

@ -0,0 +1,37 @@
package com.pixelized.shared.lwa
enum class OperatingSystem(
val home: String = System.getProperty("user.home"),
) {
Windows,
Macintosh;
companion object {
val current: OperatingSystem = run {
val name = System.getProperty("os.name")
when {
name.contains(other = "win", ignoreCase = true) -> Windows
name.contains(other = "mac", ignoreCase = true) -> Macintosh
else -> error("Unsupported operating system: $name")
}
}
}
}
fun storePath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${os.home}\\AppData\\Roaming\\Pixelized\\"
OperatingSystem.Macintosh -> "${os.home}/Library/Pixelized/"
}
}
fun characterStorePath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os)}characters\\"
OperatingSystem.Macintosh -> "${storePath(os = os)}characters/"
}
}

View file

@ -0,0 +1,9 @@
package com.pixelized.shared.lwa.model.campaign
import com.pixelized.shared.lwa.model.campaign.model.CampaignFactory
class CampaignRepository(
private val factory: CampaignFactory,
) {
}

View file

@ -0,0 +1,11 @@
package com.pixelized.shared.lwa.model.campaign.model
data class Campaign(
val characters: List<CharacterInstance>,
) {
data class CharacterInstance(
val damage: Int,
val usedPower: Int,
val usedMovement: Int,
)
}

View file

@ -0,0 +1,39 @@
package com.pixelized.shared.lwa.model.campaign.model
class CampaignFactory {
fun convertFromJson(
json: CampaignJson,
): Campaign {
return when (json) {
is CampaignJsonV1 -> convertFromV1(json = json)
}
}
private fun convertFromV1(
json: CampaignJsonV1,
): Campaign {
return Campaign(
characters = json.characters.map {
Campaign.CharacterInstance(
damage = it.damage,
usedPower = it.usedPower,
usedMovement = it.usedMovement,
)
}
)
}
private fun convertToJson(
data: Campaign,
): CampaignJson {
return CampaignJsonV1(
characters = data.characters.map {
CampaignJsonV1.CharacterInstanceJson(
damage = it.damage,
usedPower = it.usedPower,
usedMovement = it.usedMovement,
)
}
)
}
}

View file

@ -0,0 +1,6 @@
package com.pixelized.shared.lwa.model.campaign.model
import kotlinx.serialization.Serializable
@Serializable
sealed interface CampaignJson

View file

@ -0,0 +1,15 @@
package com.pixelized.shared.lwa.model.campaign.model
import kotlinx.serialization.Serializable
@Serializable
data class CampaignJsonV1(
val characters: List<CharacterInstanceJson>,
) : CampaignJson {
@Serializable
data class CharacterInstanceJson(
val damage: Int,
val usedPower: Int,
val usedMovement: Int,
)
}

View file

@ -0,0 +1,92 @@
package com.pixelized.shared.lwa.model.characterSheet.model
data class CharacterSheet(
val id: String,
val name: String,
val portrait: String?,
val thumbnail: String?,
// characteristics
val strength: Int,
val dexterity: Int,
val constitution: Int,
val height: Int,
val intelligence: Int,
val power: Int,
val charisma: Int,
// sub characteristics
val overrideMovement: Boolean,
val movement: Int,
val currentHp: Int,
val overrideMaxHp: Boolean,
val maxHp: Int,
val currentPp: Int,
val overrideMaxPP: Boolean,
val maxPp: Int,
val overrideDamageBonus: Boolean,
val damageBonus: String,
val overrideArmor: Boolean,
val armor: Int,
val overrideLearning: Boolean,
val learning: Int,
val overrideHpGrow: Boolean,
val hpGrow: Int,
// skills
val commonSkills: List<Skill>,
val specialSkills: List<Skill>,
val magicSkills: List<Skill>,
// actions
val actions: List<Roll>,
) {
data class Skill(
val id: String,
val label: String,
val description: String?,
val base: String,
val bonus: String?,
val level: String?,
val occupation: Boolean,
val used: Boolean,
)
data class Roll(
val id: String,
val label: String,
val roll: String,
)
object CommonSkillId {
const val COMBAT_ID = "COMBAT"
const val DODGE_ID = "DODGE"
const val GRAB_ID = "GRAB"
const val THROW_ID = "THROW"
const val ATHLETICS_ID = "ATHLETICS"
const val ACROBATICS_ID = "ACROBATICS"
const val PERCEPTION_ID = "PERCEPTION"
const val SEARCH_ID = "SEARCH"
const val EMPATHY_ID = "EMPATHY"
const val PERSUASION_ID = "PERSUASION"
const val INTIMIDATION_ID = "INTIMIDATION"
const val SPIEL_ID = "SPIEL"
const val BARGAIN_ID = "BARGAIN"
const val DISCRETION_ID = "DISCRETION"
const val SLEIGHT_OF_HAND_ID = "SLEIGHT_OF_HAND"
const val AID_ID = "AID"
}
object CharacteristicId {
const val STR = "STR"
const val DEX = "DEX"
const val CON = "CON"
const val HEI = "HEI"
const val INT = "INT"
const val POW = "POW"
const val CHA = "CHA"
const val MOV = "MOV"
const val HP = "HP"
const val PP = "PP"
const val DMG = "DMG"
const val ARMOR = "ARMOR"
const val LB = "LEARNING"
const val GHP = "HP_GROW"
}
}

View file

@ -0,0 +1,6 @@
package com.pixelized.shared.lwa.model.characterSheet.model
import kotlinx.serialization.Serializable
@Serializable
sealed interface CharacterSheetJson

View file

@ -0,0 +1,170 @@
package com.pixelized.shared.lwa.model.characterSheet.model
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
class CharacterSheetJsonFactory(
private val characterSheetUseCase: CharacterSheetUseCase,
) {
suspend fun convertFromJson(
json: CharacterSheetJson,
): CharacterSheet {
return when (json) {
is CharacterSheetJsonV1 -> convertFromV1(json = json)
}
}
private suspend fun convertFromV1(
json: CharacterSheetJsonV1,
): CharacterSheet = characterSheetUseCase.run {
CharacterSheet(
id = json.id,
name = json.name,
portrait = json.portrait,
thumbnail = json.thumbnail,
strength = json.strength,
dexterity = json.dexterity,
constitution = json.constitution,
height = json.height,
intelligence = json.intelligence,
power = json.power,
charisma = json.charisma,
overrideMovement = json.movement != null,
movement = json.movement ?: defaultMovement(),
currentHp = json.currentHp,
overrideMaxHp = json.maxHp != null,
maxHp = json.maxHp ?: defaultMaxHp(
constitution = json.constitution,
height = json.height,
),
currentPp = json.currentPP,
overrideMaxPP = json.maxPP != null,
maxPp = json.maxPP ?: defaultMaxPower(power = json.power),
overrideDamageBonus = json.damageBonus != null,
damageBonus = json.damageBonus ?: defaultDamageBonus(
strength = json.strength,
height = json.height,
),
overrideArmor = json.armor != null,
armor = json.armor ?: defaultArmor(),
overrideLearning = json.learning != null,
learning = json.learning ?: defaultLearning(intelligence = json.intelligence),
overrideHpGrow = json.hpGrowf != null,
hpGrow = json.hpGrowf ?: defaultHpGrow(constitution = json.constitution),
commonSkills = json.skills.map {
CharacterSheet.Skill(
id = it.id,
label = it.label,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
specialSkills = json.occupations.map {
CharacterSheet.Skill(
id = it.id,
label = it.label,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
magicSkills = json.magics.map {
CharacterSheet.Skill(
id = it.id,
label = it.label,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
actions = json.rolls.map {
CharacterSheet.Roll(
id = it.id,
label = it.label,
roll = it.roll,
)
},
)
}
fun convertToJson(
sheet: CharacterSheet,
): CharacterSheetJson {
val json = CharacterSheetJsonV1(
id = sheet.id,
name = sheet.name,
thumbnail = sheet.thumbnail,
portrait = sheet.portrait,
strength = sheet.strength,
dexterity = sheet.dexterity,
constitution = sheet.constitution,
height = sheet.height,
intelligence = sheet.intelligence,
power = sheet.power,
charisma = sheet.charisma,
movement = if (sheet.overrideMovement) sheet.movement else null,
currentHp = sheet.currentHp,
maxHp = if (sheet.overrideMaxHp) sheet.maxHp else null,
currentPP = sheet.currentPp,
maxPP = if (sheet.overrideMaxPP) sheet.maxPp else null,
damageBonus = if (sheet.overrideDamageBonus) sheet.damageBonus else null,
armor = if (sheet.overrideArmor) sheet.armor else null,
learning = if (sheet.overrideLearning) sheet.learning else null,
hpGrowf = if (sheet.overrideHpGrow) sheet.hpGrow else null,
skills = sheet.commonSkills.map {
CharacterSheetJsonV1.Skill(
id = it.id,
label = it.label,
description = null,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
occupations = sheet.specialSkills.map {
CharacterSheetJsonV1.Skill(
id = it.id,
label = it.label,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
magics = sheet.magicSkills.map {
CharacterSheetJsonV1.Skill(
id = it.id,
label = it.label,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,
occupation = it.occupation,
used = it.used,
)
},
rolls = sheet.actions.map {
CharacterSheetJsonV1.Roll(
id = it.id,
label = it.label,
roll = it.roll,
)
},
)
return json
}
}

View file

@ -0,0 +1,57 @@
package com.pixelized.shared.lwa.model.characterSheet.model
import kotlinx.serialization.Serializable
@Serializable
data class CharacterSheetJsonV1(
val id: String,
val name: String,
val portrait: String?,
val thumbnail: String?,
// characteristics
val strength: Int,
val dexterity: Int,
val constitution: Int,
val height: Int,
val intelligence: Int,
val power: Int,
val charisma: Int,
// sub characteristics
val movement: Int?,
val currentHp: Int,
val maxHp: Int?,
val currentPP: Int,
val maxPP: Int?,
val damageBonus: String?,
val armor: Int?,
val learning: Int?,
val hpGrowf: Int?,
// skills
val skills: List<Skill>,
// occupations
val occupations: List<Skill>,
// magic skill
val magics: List<Skill>,
// attack
val rolls: List<Roll>,
) : CharacterSheetJson {
@Serializable
data class Skill(
val id: String,
val label: String,
val description: String?,
val base: String,
val bonus: String?,
val level: String?,
val occupation: Boolean,
val used: Boolean,
)
@Serializable
data class Roll(
val id: String,
val label: String,
val roll: String,
)
}

View file

@ -1,4 +1,4 @@
package com.pixelized.server.lwa.protocol
package com.pixelized.shared.lwa.protocol
import kotlinx.serialization.Serializable

View file

@ -0,0 +1,5 @@
package com.pixelized.shared.lwa.protocol
enum class MessageType {
Roll
}

View file

@ -1,4 +1,4 @@
package com.pixelized.server.lwa.protocol.roll
package com.pixelized.shared.lwa.protocol.roll
import kotlinx.serialization.Serializable

View file

@ -0,0 +1,56 @@
package com.pixelized.shared.lwa.usecase
import kotlin.math.ceil
import kotlin.math.max
class CharacterSheetUseCase {
fun normalize(value: Int): Int {
return value - value % 5 // (truncate(value.toFloat() / 5f) * 5f).toInt()
}
fun defaultMovement(): Int = 10
fun defaultMaxHp(
constitution: Int,
height: Int,
): Int {
return (ceil((constitution + height) / 2f).toInt())
}
fun defaultMaxPower(
power: Int,
): Int {
return power
}
fun defaultDamageBonus(
strength: Int,
height: Int,
): String {
return defaultDamageBonus(sum = strength + height)
}
fun defaultDamageBonus(
sum: Int,
): String {
return when {
sum < 12 -> "-1d6"
sum in 12..17 -> "-1d4"
sum in 18..22 -> "+0"
sum in 23..29 -> "+1d4"
sum in 30..39 -> "+1d6"
else -> "+2d6"
}
}
fun defaultArmor(): Int = 0
fun defaultLearning(intelligence: Int): Int {
return max(0, (intelligence - 10) * 2)
}
fun defaultHpGrow(constitution: Int): Int {
return (constitution / 3)
}
}