Add Addable InventoryItem feature & quantity change.

This commit is contained in:
Thomas Andres Gomez 2025-04-19 19:37:55 +02:00
parent 48074f3d13
commit 9fce3f1cb8
28 changed files with 785 additions and 343 deletions

View file

@ -49,4 +49,15 @@ class InventoryService(
fun delete(characterSheetId: String) {
inventoryStore.delete(characterSheetId = characterSheetId)
}
@Throws
fun createItem(
characterSheetId: String,
itemId: String,
) : String {
return inventoryStore.createItem(
characterSheetId = characterSheetId,
itemId = itemId,
)
}
}

View file

@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.File
import java.util.UUID
class InventoryStore(
private val pathProvider: PathProvider,
@ -152,7 +153,49 @@ class InventoryStore(
}
}
@Throws
fun createItem(
characterSheetId: String,
itemId: String,
): String {
val (updatedInventory, inventoryId) = inventoryFlow.value.toMutableMap().let { characters ->
// get the inventory of the character, if none create one.
val inventory = characters[characterSheetId]
?: Inventory.empty(characterSheetId = characterSheetId)
// create an inventoryId.
val inventoryId = inventory.items.createInventoryId()
// create an inventory Item.
val item = Inventory.Item(
inventoryId = inventoryId,
itemId = itemId,
count = 1f,
equipped = false,
)
// update the inventory with the updated item.
inventory.copy(
items = inventory.items.toMutableList().also {
it.add(item)
}
) to inventoryId
}
// save the inventory
save(
inventory = updatedInventory,
create = false,
)
// return the inventory ID.
return inventoryId
}
private fun inventoryFile(id: String): File {
return File("${pathProvider.inventoryPath()}${id}.json")
}
private fun List<Inventory.Item>.createInventoryId(): String {
var inventoryId: String
do {
inventoryId = UUID.randomUUID().toString()
} while (any { inventoryId == it.inventoryId })
return inventoryId
}
}

View file

@ -19,6 +19,7 @@ import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
import com.pixelized.server.lwa.server.rest.character.putCharacterDamage
import com.pixelized.server.lwa.server.rest.character.putCharacterDiminished
import com.pixelized.server.lwa.server.rest.character.putCharacterFatigue
import com.pixelized.server.lwa.server.rest.inventory.createInventory
import com.pixelized.server.lwa.server.rest.inventory.deleteInventory
import com.pixelized.server.lwa.server.rest.inventory.getInventory
import com.pixelized.server.lwa.server.rest.inventory.putInventory
@ -258,6 +259,10 @@ class LocalServer {
path = "/update",
body = engine.putInventory()
)
put(
path = "/create",
body = engine.createInventory()
)
delete(
path = "/delete",
body = engine.deleteInventory()

View file

@ -1,4 +1,6 @@
package com.pixelized.server.lwa.server.exception
class MissingParameterException(name: String) :
import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode
class MissingParameterException(name: String, val code: ErrorCode) :
ServerException(root = Exception("Missing '$name' parameter."))

View file

@ -1,9 +1,9 @@
package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.server.lwa.utils.extentions.param
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond
@ -14,8 +14,10 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
try {
// get the query parameter
val characterSheetId = call.queryParameters.characterSheetId
val damage = call.queryParameters["damage"]?.toIntOrNull()
?: throw MissingParameterException(name = "damage")
val damage: Int = call.queryParameters.param(
name = "damage",
code = APIResponse.ErrorCode.Damage,
)
// fetch the character sheet
val characterSheet = characterService.character(characterSheetId)
?: error("CharacterSheet with id:$characterSheetId not found.")

View file

@ -1,9 +1,9 @@
package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.server.lwa.utils.extentions.param
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond
@ -14,8 +14,10 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
try {
// get the query parameter
val characterSheetId = call.queryParameters.characterSheetId
val diminished = call.queryParameters["diminished"]?.toIntOrNull()
?: throw MissingParameterException(name = "diminished")
val diminished: Int = call.queryParameters.param(
name = "diminished",
code = APIResponse.ErrorCode.Diminished,
)
// Update the character damage
characterService.updateDiminished(
characterSheetId = characterSheetId,

View file

@ -4,6 +4,7 @@ import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.server.lwa.utils.extentions.param
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond
@ -14,8 +15,10 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
try {
// get the query parameter
val characterSheetId = call.queryParameters.characterSheetId
val fatigue = call.queryParameters["fatigue"]?.toIntOrNull()
?: throw MissingParameterException(name = "fatigue")
val fatigue: Int = call.queryParameters.param(
name = "fatigue",
code = APIResponse.ErrorCode.Fatigue,
)
// fetch the character sheet
val characterSheet = characterService.character(characterSheetId)
?: error("CharacterSheet with id:$characterSheetId not found.")

View file

@ -0,0 +1,41 @@
package com.pixelized.server.lwa.server.rest.inventory
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.server.lwa.utils.extentions.itemId
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.createInventory(): suspend RoutingContext.() -> Unit {
return {
try {
// get the query parameter
val characterSheetId = call.queryParameters.characterSheetId
val itemId = call.queryParameters.itemId
// add the item to the inventory.
val inventoryId = inventoryService.createItem(
characterSheetId = characterSheetId,
itemId = itemId,
)
// API & WebSocket responses.
call.respond(
message = APIResponse.success(
data = inventoryId,
),
)
webSocket.emit(
value = ApiSynchronisation.InventoryUpdate(
timestamp = System.currentTimeMillis(),
characterSheetId = characterSheetId,
),
)
} catch (exception: Exception) {
call.exception(
exception = exception,
)
}
}
}

View file

@ -13,7 +13,7 @@ fun Engine.deleteInventory(): suspend RoutingContext.() -> Unit {
try {
// get the query parameter
val characterSheetId = call.queryParameters.characterSheetId
// delete the alteration.
// delete the inventory.
inventoryService.delete(
characterSheetId = characterSheetId,
)

View file

@ -1,29 +1,49 @@
package com.pixelized.server.lwa.utils.extentions
import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.http.Parameters
val Parameters.characterSheetId
get() = "characterSheetId".let { param ->
this[param] ?: throw MissingParameterException(name = param)
}
inline fun <reified T> Parameters.param(
name: String,
code: APIResponse.ErrorCode,
): T {
return when (T::class) {
String::class -> this[name] as? T
Boolean::class -> this[name]?.toBooleanStrictOrNull() as? T
else -> null
} ?: throw MissingParameterException(
name = name,
code = code,
)
}
val Parameters.alterationId
get() = "alterationId".let { param ->
this[param] ?: throw MissingParameterException(name = param)
}
val Parameters.characterSheetId: String
get() = param(
name = "characterSheetId",
code = APIResponse.ErrorCode.CharacterSheetId,
)
val Parameters.itemId
get() = "itemId".let { param ->
this[param] ?: throw MissingParameterException(name = param)
}
val Parameters.alterationId: String
get() = param(
name = "alterationId",
code = APIResponse.ErrorCode.AlterationId,
)
val Parameters.create
get() = "create".let { param ->
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
}
val Parameters.itemId: String
get() = param(
name = "itemId",
code = APIResponse.ErrorCode.ItemId,
)
val Parameters.active
get() = "active".let { param ->
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
}
val Parameters.create: Boolean
get() = param(
name = "create",
code = APIResponse.ErrorCode.Create,
)
val Parameters.active: Boolean
get() = param(
name = "active",
code = APIResponse.ErrorCode.Active,
)

View file

@ -15,7 +15,7 @@ suspend inline fun <reified T : Exception> RoutingCall.exception(exception: T) {
message = APIResponse.error(
status = APIResponse.BAD_REQUEST,
message = exception.message ?: "?",
code = APIResponse.ErrorCode.AlterationName,
code = exception.code,
)
)
}