This commit was merged in pull request #128.
This commit is contained in:
@@ -37,6 +37,8 @@ object TestTags {
|
||||
const val ATTRIBUTE_EDIT_CONFIRM = "attribute_edit_confirm"
|
||||
const val ATTRIBUTE_EDIT_DISMISS = "attribute_edit_dismiss"
|
||||
const val ATTRIBUTE_EDIT_ERROR = "attribute_edit_error"
|
||||
const val ATTRIBUTE_EDIT_AUGMENTED_TOGGLE = "attribute_edit_augmented_toggle"
|
||||
const val ATTRIBUTE_EDIT_AUGMENTED_INDICATOR = "attribute_edit_augmented_indicator"
|
||||
|
||||
// --- Damage monitor interaction ---
|
||||
const val DAMAGE_WOUND_MODIFIER = "damage_wound_modifier"
|
||||
|
||||
+3
@@ -19,6 +19,7 @@ import org.shahondin1624.lib.functions.performExtendedTest
|
||||
import org.shahondin1624.lib.functions.performOpposedTest
|
||||
import org.shahondin1624.lib.functions.performSimpleTest
|
||||
import org.shahondin1624.model.attributes.AttributeType
|
||||
import org.shahondin1624.model.attributes.MetatypeAttributeLimits
|
||||
import org.shahondin1624.model.charactermodel.ShadowrunCharacter
|
||||
import org.shahondin1624.model.dice.ExtendedTestResult
|
||||
import org.shahondin1624.model.dice.OpposedTestResult
|
||||
@@ -164,8 +165,10 @@ fun CharacterSheetPage(
|
||||
// Show attribute edit dialog
|
||||
editingAttributeType?.let { attrType ->
|
||||
val attr = character.attributes.getAttributeByType(attrType)
|
||||
val racialMax = MetatypeAttributeLimits.getMaximum(character.characterData.metatype, attrType)
|
||||
AttributeEditDialog(
|
||||
attribute = attr,
|
||||
maxValue = racialMax,
|
||||
onConfirm = { newValue ->
|
||||
onUpdateCharacter { char ->
|
||||
val newAttributes = char.attributes.withAttribute(attrType, newValue)
|
||||
|
||||
+44
-6
@@ -11,21 +11,28 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.shahondin1624.lib.components.TestTags
|
||||
import org.shahondin1624.model.attributes.Attribute
|
||||
import org.shahondin1624.model.attributes.MetatypeAttributeLimits
|
||||
|
||||
/**
|
||||
* Dialog for editing an attribute value.
|
||||
* Provides a number input with validation for minimum 1 and configurable maximum.
|
||||
* Provides a number input with validation against Shadowrun 5e racial maximums.
|
||||
* Supports augmented mode where values can exceed the racial maximum by up to +4.
|
||||
*/
|
||||
@Composable
|
||||
fun AttributeEditDialog(
|
||||
attribute: Attribute,
|
||||
maxValue: Int = 10,
|
||||
maxValue: Int = 6,
|
||||
onConfirm: (Int) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var textValue by remember { mutableStateOf(attribute.value.toString()) }
|
||||
var isAugmented by remember { mutableStateOf(false) }
|
||||
|
||||
val augmentedMaxValue = maxValue + MetatypeAttributeLimits.AUGMENTATION_BONUS
|
||||
val effectiveMax = if (isAugmented) augmentedMaxValue else maxValue
|
||||
val parsedValue = textValue.toIntOrNull()
|
||||
val isValid = parsedValue != null && parsedValue in 1..maxValue
|
||||
val isValid = parsedValue != null && parsedValue in 1..effectiveMax
|
||||
val isAboveRacialMax = parsedValue != null && parsedValue > maxValue && parsedValue <= augmentedMaxValue
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
@@ -45,7 +52,7 @@ fun AttributeEditDialog(
|
||||
textValue = newValue
|
||||
}
|
||||
},
|
||||
label = { Text("Value (1-$maxValue)") },
|
||||
label = { Text("Value (1-$effectiveMax)") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
isError = !isValid && textValue.isNotEmpty(),
|
||||
singleLine = true,
|
||||
@@ -53,12 +60,43 @@ fun AttributeEditDialog(
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.ATTRIBUTE_EDIT_INPUT)
|
||||
)
|
||||
|
||||
// Augmented mode toggle
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.testTag(TestTags.ATTRIBUTE_EDIT_AUGMENTED_TOGGLE)
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isAugmented,
|
||||
onCheckedChange = { isAugmented = it }
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "Augmented (max $augmentedMaxValue)",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
// Visual indicator when value exceeds racial max but is within augmented max
|
||||
if (isAugmented && isAboveRacialMax && isValid) {
|
||||
Text(
|
||||
text = "Augmented: exceeds racial maximum of $maxValue",
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.testTag(TestTags.ATTRIBUTE_EDIT_AUGMENTED_INDICATOR)
|
||||
)
|
||||
}
|
||||
|
||||
if (!isValid && textValue.isNotEmpty()) {
|
||||
Text(
|
||||
text = if (parsedValue != null && parsedValue < 1) {
|
||||
"Minimum value is 1"
|
||||
} else if (parsedValue != null && parsedValue > maxValue) {
|
||||
"Maximum value is $maxValue"
|
||||
} else if (parsedValue != null && parsedValue > effectiveMax) {
|
||||
if (isAugmented) {
|
||||
"Maximum augmented value is $augmentedMaxValue"
|
||||
} else {
|
||||
"Maximum value is $maxValue (enable augmented for higher)"
|
||||
}
|
||||
} else {
|
||||
"Enter a valid number"
|
||||
},
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package org.shahondin1624.model.attributes
|
||||
|
||||
import org.shahondin1624.model.characterdata.Metatype
|
||||
|
||||
/**
|
||||
* Shadowrun 5e racial attribute maximums per metatype.
|
||||
* Unaugmented attributes cannot exceed these values.
|
||||
* Augmented attributes may exceed racial max by up to +4.
|
||||
*/
|
||||
object MetatypeAttributeLimits {
|
||||
|
||||
/** Maximum additional points augmentation can add above the racial max. */
|
||||
const val AUGMENTATION_BONUS = 4
|
||||
|
||||
private data class AttributeLimits(
|
||||
val body: Int,
|
||||
val agility: Int,
|
||||
val reaction: Int,
|
||||
val strength: Int,
|
||||
val willpower: Int,
|
||||
val logic: Int,
|
||||
val intuition: Int,
|
||||
val charisma: Int,
|
||||
val edge: Int
|
||||
)
|
||||
|
||||
private val limits: Map<Metatype, AttributeLimits> = mapOf(
|
||||
Metatype.Human to AttributeLimits(
|
||||
body = 6, agility = 6, reaction = 6, strength = 6,
|
||||
willpower = 6, logic = 6, intuition = 6, charisma = 6, edge = 7
|
||||
),
|
||||
Metatype.Elf to AttributeLimits(
|
||||
body = 6, agility = 7, reaction = 6, strength = 6,
|
||||
willpower = 6, logic = 6, intuition = 6, charisma = 8, edge = 6
|
||||
),
|
||||
Metatype.Dwarf to AttributeLimits(
|
||||
body = 8, agility = 6, reaction = 5, strength = 8,
|
||||
willpower = 7, logic = 6, intuition = 6, charisma = 6, edge = 6
|
||||
),
|
||||
Metatype.Ork to AttributeLimits(
|
||||
body = 9, agility = 6, reaction = 6, strength = 8,
|
||||
willpower = 6, logic = 5, intuition = 6, charisma = 5, edge = 6
|
||||
),
|
||||
Metatype.Troll to AttributeLimits(
|
||||
body = 10, agility = 5, reaction = 6, strength = 10,
|
||||
willpower = 6, logic = 5, intuition = 5, charisma = 4, edge = 6
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns the unaugmented racial maximum for the given metatype and attribute type.
|
||||
*/
|
||||
fun getMaximum(metatype: Metatype, attributeType: AttributeType): Int {
|
||||
val l = limits[metatype] ?: return 6 // fallback to human-like default
|
||||
return when (attributeType) {
|
||||
AttributeType.Body -> l.body
|
||||
AttributeType.Agility -> l.agility
|
||||
AttributeType.Reaction -> l.reaction
|
||||
AttributeType.Strength -> l.strength
|
||||
AttributeType.Willpower -> l.willpower
|
||||
AttributeType.Logic -> l.logic
|
||||
AttributeType.Intuition -> l.intuition
|
||||
AttributeType.Charisma -> l.charisma
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum Edge value for the given metatype.
|
||||
*/
|
||||
fun getEdgeMaximum(metatype: Metatype): Int {
|
||||
return limits[metatype]?.edge ?: 7
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the augmented maximum (racial max + augmentation bonus) for the given metatype and attribute.
|
||||
*/
|
||||
fun getAugmentedMaximum(metatype: Metatype, attributeType: AttributeType): Int {
|
||||
return getMaximum(metatype, attributeType) + AUGMENTATION_BONUS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.shahondin1624
|
||||
|
||||
import org.shahondin1624.model.attributes.AttributeType
|
||||
import org.shahondin1624.model.attributes.MetatypeAttributeLimits
|
||||
import org.shahondin1624.model.characterdata.Metatype
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Unit tests for MetatypeAttributeLimits ensuring correct Shadowrun 5e racial maximums.
|
||||
*/
|
||||
class MetatypeAttributeLimitsTest {
|
||||
|
||||
@Test
|
||||
fun humanAttributeMaximumsAreCorrect() {
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Body))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Agility))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Reaction))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Strength))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Willpower))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Logic))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Intuition))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Human, AttributeType.Charisma))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun humanEdgeMaximumIs7() {
|
||||
assertEquals(7, MetatypeAttributeLimits.getEdgeMaximum(Metatype.Human))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun elfHasHigherAgilityAndCharisma() {
|
||||
assertEquals(7, MetatypeAttributeLimits.getMaximum(Metatype.Elf, AttributeType.Agility))
|
||||
assertEquals(8, MetatypeAttributeLimits.getMaximum(Metatype.Elf, AttributeType.Charisma))
|
||||
assertEquals(6, MetatypeAttributeLimits.getMaximum(Metatype.Elf, AttributeType.Body))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dwarfHasHigherBodyStrengthWillpower() {
|
||||
assertEquals(8, MetatypeAttributeLimits.getMaximum(Metatype.Dwarf, AttributeType.Body))
|
||||
assertEquals(8, MetatypeAttributeLimits.getMaximum(Metatype.Dwarf, AttributeType.Strength))
|
||||
assertEquals(7, MetatypeAttributeLimits.getMaximum(Metatype.Dwarf, AttributeType.Willpower))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Dwarf, AttributeType.Reaction))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun orkHasHigherBodyAndStrength() {
|
||||
assertEquals(9, MetatypeAttributeLimits.getMaximum(Metatype.Ork, AttributeType.Body))
|
||||
assertEquals(8, MetatypeAttributeLimits.getMaximum(Metatype.Ork, AttributeType.Strength))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Ork, AttributeType.Logic))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Ork, AttributeType.Charisma))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trollHasHighestBodyAndStrength() {
|
||||
assertEquals(10, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Body))
|
||||
assertEquals(10, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Strength))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Agility))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Logic))
|
||||
assertEquals(5, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Intuition))
|
||||
assertEquals(4, MetatypeAttributeLimits.getMaximum(Metatype.Troll, AttributeType.Charisma))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trollEdgeMaximumIs6() {
|
||||
assertEquals(6, MetatypeAttributeLimits.getEdgeMaximum(Metatype.Troll))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun augmentedMaximumAdds4ToRacialMax() {
|
||||
assertEquals(10, MetatypeAttributeLimits.getAugmentedMaximum(Metatype.Human, AttributeType.Body))
|
||||
assertEquals(14, MetatypeAttributeLimits.getAugmentedMaximum(Metatype.Troll, AttributeType.Body))
|
||||
assertEquals(12, MetatypeAttributeLimits.getAugmentedMaximum(Metatype.Elf, AttributeType.Charisma))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allMetatypesHaveEdgeMaximums() {
|
||||
for (metatype in Metatype.entries) {
|
||||
val edgeMax = MetatypeAttributeLimits.getEdgeMaximum(metatype)
|
||||
assert(edgeMax in 1..10) { "Edge max for $metatype should be reasonable, got $edgeMax" }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allMetatypesHaveAllAttributeMaximums() {
|
||||
for (metatype in Metatype.entries) {
|
||||
for (attrType in AttributeType.entries) {
|
||||
val max = MetatypeAttributeLimits.getMaximum(metatype, attrType)
|
||||
assert(max in 1..15) { "Max for $metatype/$attrType should be reasonable, got $max" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user