Add starter pet templates
This commit is contained in:
@@ -6,6 +6,7 @@ It is designed to feel like a simple handheld digital pet: feed your pet, bathe
|
||||
|
||||
## Features
|
||||
|
||||
- Starter pet templates
|
||||
- Offline/local-first virtual pet
|
||||
- LCD-style toy-inspired interface
|
||||
- Care actions: feed, bathe, nap, walk, potty, pet, and talk
|
||||
|
||||
@@ -72,6 +72,75 @@ private data class CareJournalEntry(
|
||||
val happenedAtMs: Long
|
||||
)
|
||||
|
||||
private data class PetTemplate(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val topLine: String,
|
||||
val facePrefix: String,
|
||||
val faceSuffix: String,
|
||||
val feetLine: String
|
||||
)
|
||||
|
||||
private val lcdPetTemplates = listOf(
|
||||
PetTemplate(
|
||||
id = "classic",
|
||||
label = "Classic",
|
||||
topLine = " /---\\ \n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /| |\\ "
|
||||
),
|
||||
PetTemplate(
|
||||
id = "scruffy",
|
||||
label = "Scruffy",
|
||||
topLine = " /^^^\\ \n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /|_|\\ "
|
||||
),
|
||||
PetTemplate(
|
||||
id = "bean",
|
||||
label = "Round Bean",
|
||||
topLine = " .---. \n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /|\\ "
|
||||
),
|
||||
PetTemplate(
|
||||
id = "pocketpup",
|
||||
label = "Pocket Pup",
|
||||
topLine = " /\\_/\\ \n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /|\\ "
|
||||
),
|
||||
PetTemplate(
|
||||
id = "batty",
|
||||
label = "Batty",
|
||||
topLine = "/\\---/\\\n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /|\\ "
|
||||
),
|
||||
PetTemplate(
|
||||
id = "goblin",
|
||||
label = "Goblin",
|
||||
topLine = " /\\_/\\ \n",
|
||||
facePrefix = "( ",
|
||||
faceSuffix = " )\n",
|
||||
feetLine = " /| |\\ "
|
||||
)
|
||||
)
|
||||
|
||||
private fun lcdPetTemplateById(id: String): PetTemplate {
|
||||
return lcdPetTemplates.firstOrNull { it.id == id } ?: lcdPetTemplates.first()
|
||||
}
|
||||
|
||||
private fun renderPetTemplateText(template: PetTemplate, expression: String): String {
|
||||
return template.topLine + template.facePrefix + expression + template.faceSuffix + template.feetLine
|
||||
}
|
||||
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -376,6 +445,8 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
var pottyFaceBag by remember { mutableStateOf(emptyList<String>()) }
|
||||
var petName by remember { mutableStateOf("") }
|
||||
var pendingPetName by remember { mutableStateOf("") }
|
||||
var petTemplateId by remember { mutableStateOf("classic") }
|
||||
var pendingPetTemplateId by remember { mutableStateOf("classic") }
|
||||
var happyOutsideTrip by remember { mutableStateOf(false) }
|
||||
|
||||
var petAgeDays by remember { mutableIntStateOf(0) }
|
||||
@@ -583,6 +654,8 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
currentWant = save.currentWant
|
||||
petName = save.petName
|
||||
pendingPetName = save.petName
|
||||
petTemplateId = save.petTemplateId.ifBlank { "classic" }
|
||||
pendingPetTemplateId = petTemplateId
|
||||
isSleeping = save.isSleeping
|
||||
sleepMode = save.sleepMode
|
||||
roughWakeAlert = save.roughWakeAlert
|
||||
@@ -648,7 +721,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
|
||||
val petAgeText = statAgeText(petAgeDays)
|
||||
val displayPetName = petName.ifBlank { "Pet" }
|
||||
val needsPetName = saveLoaded && petName.isBlank() && !isTradingInPet && !isFindingNewPet
|
||||
val needsPetName = saveLoaded && (petName.isBlank() || petTemplateId.isBlank()) && !isTradingInPet && !isFindingNewPet
|
||||
val trimmedPendingPetName = pendingPetName.trim()
|
||||
val normalizedPendingPetName = trimmedPendingPetName.lowercase()
|
||||
val isNameValidationActive = !testingMode
|
||||
@@ -656,6 +729,8 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
val isReservedPetName = isNameValidationActive && normalizedPendingPetName in reservedPetNames
|
||||
val hasValidPetNameLetters = trimmedPendingPetName.isNotEmpty() && trimmedPendingPetName.all { it.isLetter() }
|
||||
val canKeepPetName = hasValidPetNameLetters && !isReservedPetName
|
||||
val pendingPetTemplate = lcdPetTemplateById(pendingPetTemplateId)
|
||||
val selectedPetTemplate = lcdPetTemplateById(petTemplateId)
|
||||
|
||||
val bondRelationship = when {
|
||||
bond >= 100 -> "BFF"
|
||||
@@ -796,13 +871,13 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
val petArt = buildAnnotatedString {
|
||||
append(" /---\\ \n")
|
||||
append("( ")
|
||||
append(selectedPetTemplate.topLine)
|
||||
append(selectedPetTemplate.facePrefix)
|
||||
withStyle(SpanStyle(fontWeight = if (pottyFaceOverride.isNotEmpty()) FontWeight.Bold else FontWeight.Normal)) {
|
||||
append(petExpression)
|
||||
}
|
||||
append(" )\n")
|
||||
append(" /| |\\ ")
|
||||
append(selectedPetTemplate.faceSuffix)
|
||||
append(selectedPetTemplate.feetLine)
|
||||
}
|
||||
|
||||
val petColor = if (status == "Sick") sickGreen else lcdDark
|
||||
@@ -1343,6 +1418,8 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
careJournalEntries = emptyList()
|
||||
petName = ""
|
||||
pendingPetName = ""
|
||||
petTemplateId = "classic"
|
||||
pendingPetTemplateId = "classic"
|
||||
happyOutsideTrip = false
|
||||
petAgeDays = 0
|
||||
careScore = 0
|
||||
@@ -1397,6 +1474,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
tradeInProgress = 0,
|
||||
currentWant = currentWant,
|
||||
petName = petName,
|
||||
petTemplateId = petTemplateId,
|
||||
isSleeping = isSleeping,
|
||||
sleepMode = sleepMode,
|
||||
roughWakeAlert = roughWakeAlert,
|
||||
@@ -1438,6 +1516,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
tradeInProgress = 0,
|
||||
currentWant = currentWant,
|
||||
petName = petName,
|
||||
petTemplateId = petTemplateId,
|
||||
isSleeping = isSleeping,
|
||||
sleepMode = sleepMode,
|
||||
roughWakeAlert = roughWakeAlert,
|
||||
@@ -1652,6 +1731,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
tradeInProgress = tradeInProgress,
|
||||
currentWant = currentWant,
|
||||
petName = petName,
|
||||
petTemplateId = petTemplateId,
|
||||
isSleeping = isSleeping,
|
||||
sleepMode = sleepMode,
|
||||
roughWakeAlert = roughWakeAlert,
|
||||
@@ -1700,6 +1780,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
tradeInProgress = tradeInProgressBlocks,
|
||||
currentWant = currentWant,
|
||||
petName = petName,
|
||||
petTemplateId = petTemplateId,
|
||||
isSleeping = isSleeping,
|
||||
sleepMode = sleepMode,
|
||||
roughWakeAlert = roughWakeAlert,
|
||||
@@ -2310,6 +2391,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
tradeInProgress = tradeInProgress,
|
||||
currentWant = currentWant,
|
||||
petName = petName,
|
||||
petTemplateId = petTemplateId,
|
||||
isSleeping = true,
|
||||
sleepMode = "Auto",
|
||||
roughWakeAlert = roughWakeAlert,
|
||||
@@ -2470,7 +2552,75 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "NAME NEW PET",
|
||||
text = "CHOOSE NEW PET",
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "PET TEMPLATE",
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Selected: ${pendingPetTemplate.label}",
|
||||
color = lcdDark,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
lcdPetTemplates.chunked(2).forEach { rowTemplates ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
rowTemplates.forEach { template ->
|
||||
val isSelectedTemplate = pendingPetTemplateId == template.id
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.border(
|
||||
width = if (isSelectedTemplate) 2.dp else 1.dp,
|
||||
color = if (isSelectedTemplate) lcdDark else lcdFaded
|
||||
)
|
||||
.clickable {
|
||||
pendingPetTemplateId = template.id
|
||||
}
|
||||
.padding(vertical = 4.dp, horizontal = 2.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = renderPetTemplateText(template, "^_^"),
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
textAlign = TextAlign.Center,
|
||||
color = lcdDark
|
||||
)
|
||||
Text(
|
||||
text = template.label.uppercase(),
|
||||
fontSize = 10.sp,
|
||||
lineHeight = 10.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = lcdDark
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "NAME PET",
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -2522,6 +2672,7 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
lcdDark = lcdDark,
|
||||
lcdFaded = lcdFaded,
|
||||
onClick = {
|
||||
petTemplateId = pendingPetTemplateId
|
||||
petName = trimmedPendingPetName.take(12)
|
||||
pendingPetName = petName
|
||||
statsMenuOpen = false
|
||||
|
||||
@@ -35,6 +35,7 @@ data class LCDPetRecordSave(
|
||||
val tradeInProgress: Int = 0,
|
||||
val currentWant: String = "",
|
||||
val petName: String = "",
|
||||
val petTemplateId: String = "classic",
|
||||
val isSleeping: Boolean = false,
|
||||
val sleepMode: String = "",
|
||||
val roughWakeAlert: Boolean = false,
|
||||
@@ -66,6 +67,7 @@ private object LCDPetSaveKeys {
|
||||
val TRADE_IN_PROGRESS = intPreferencesKey("trade_in_progress")
|
||||
val CURRENT_WANT = stringPreferencesKey("current_want")
|
||||
val PET_NAME = stringPreferencesKey("pet_name")
|
||||
val PET_TEMPLATE_ID = stringPreferencesKey("pet_template_id")
|
||||
val IS_SLEEPING = booleanPreferencesKey("is_sleeping")
|
||||
val SLEEP_MODE = stringPreferencesKey("sleep_mode")
|
||||
val ROUGH_WAKE_ALERT = booleanPreferencesKey("rough_wake_alert")
|
||||
@@ -99,6 +101,7 @@ val Context.lcdPetRecordSaveFlow: Flow<LCDPetRecordSave>
|
||||
tradeInProgress = preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] ?: 0,
|
||||
currentWant = preferences[LCDPetSaveKeys.CURRENT_WANT] ?: "",
|
||||
petName = preferences[LCDPetSaveKeys.PET_NAME] ?: "",
|
||||
petTemplateId = preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] ?: "classic",
|
||||
isSleeping = preferences[LCDPetSaveKeys.IS_SLEEPING] ?: false,
|
||||
sleepMode = preferences[LCDPetSaveKeys.SLEEP_MODE] ?: "",
|
||||
roughWakeAlert = preferences[LCDPetSaveKeys.ROUGH_WAKE_ALERT] ?: false,
|
||||
@@ -131,6 +134,7 @@ suspend fun Context.saveLCDPetStats(
|
||||
tradeInProgress: Int,
|
||||
currentWant: String,
|
||||
petName: String,
|
||||
petTemplateId: String = "classic",
|
||||
isSleeping: Boolean,
|
||||
sleepMode: String,
|
||||
roughWakeAlert: Boolean,
|
||||
@@ -160,6 +164,7 @@ suspend fun Context.saveLCDPetStats(
|
||||
preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] = tradeInProgress
|
||||
preferences[LCDPetSaveKeys.CURRENT_WANT] = currentWant
|
||||
preferences[LCDPetSaveKeys.PET_NAME] = petName
|
||||
preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] = petTemplateId
|
||||
preferences[LCDPetSaveKeys.IS_SLEEPING] = isSleeping
|
||||
preferences[LCDPetSaveKeys.SLEEP_MODE] = sleepMode
|
||||
preferences[LCDPetSaveKeys.ROUGH_WAKE_ALERT] = roughWakeAlert
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Initial v1.0 release candidate.
|
||||
|
||||
Adds the core LCDPet care loop, local save/load, Wants, care status tracking, optional local notifications, Credits / Licenses screen, and offline-first Android gameplay.
|
||||
Adds starter pet templates, the core LCDPet care loop, local save/load, Wants, care status tracking, optional local notifications, Credits / Licenses screen, and offline-first Android gameplay.
|
||||
|
||||
@@ -3,6 +3,7 @@ LCDPet is a tiny offline LCD-style virtual pet for Android.
|
||||
Feed your pet, bathe it, let it nap, take it outside, handle random Wants, and keep an eye on its care status. The app is designed around a simple handheld digital-pet feel with local progress, toy-like interactions, and a low-pressure care loop.
|
||||
|
||||
Features:
|
||||
* Starter pet templates
|
||||
|
||||
* Offline/local-first virtual pet
|
||||
* LCD-style toy-inspired interface
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Reference in New Issue
Block a user