Add characterSheet and Campaing to the server.
This commit is contained in:
parent
1e5f0d88ae
commit
495768e5fe
53 changed files with 879 additions and 513 deletions
|
|
@ -22,4 +22,5 @@ dependencies {
|
|||
implementation(libs.ktor.server.websockets)
|
||||
implementation(libs.ktor.server.negotiation)
|
||||
implementation(libs.ktor.serialization.json)
|
||||
implementation(libs.filament.android)
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import com.pixelized.server.lwa.model.character.CharacterSheetRepository
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
|
@ -9,17 +12,25 @@ val serverModuleDependencies
|
|||
factoryDependencies,
|
||||
useCaseDependencies,
|
||||
storeDependencies,
|
||||
repositoryDependencies,
|
||||
serviceDependencies,
|
||||
engineDependencies,
|
||||
)
|
||||
|
||||
val engineDependencies
|
||||
get() = module {
|
||||
singleOf(::Engine)
|
||||
}
|
||||
|
||||
val storeDependencies
|
||||
get() = module {
|
||||
singleOf(::CharacterSheetStore)
|
||||
singleOf(::CampaignStore)
|
||||
}
|
||||
|
||||
val repositoryDependencies
|
||||
val serviceDependencies
|
||||
get() = module {
|
||||
singleOf(::CharacterSheetRepository)
|
||||
singleOf(::CharacterSheetService)
|
||||
singleOf(::CampaignService)
|
||||
}
|
||||
|
||||
val factoryDependencies
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage
|
||||
import com.pixelized.shared.lwa.usecase.CampaignUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CampaignService(
|
||||
private val store: CampaignStore,
|
||||
private val factory: CampaignJsonFactory,
|
||||
private val useCase: CampaignUseCase,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
private val campaign = store.campaignFlow().stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = Campaign.EMPTY,
|
||||
)
|
||||
|
||||
fun campaign(): CampaignJson {
|
||||
return campaign.value.let(factory::convertToJson)
|
||||
}
|
||||
|
||||
suspend fun update(
|
||||
message: UpdatePlayerCharacteristicMessage,
|
||||
) {
|
||||
// fetch all the current campaign character
|
||||
val characters = campaign.value.characters.toMutableMap()
|
||||
// update the corresponding character using the usecase
|
||||
characters[message.characterId] = useCase.updateCharacteristic(
|
||||
character = characters[message.characterId] ?: Campaign.CharacterInstance(
|
||||
characteristic = emptyMap(),
|
||||
usedSkill = emptyList(),
|
||||
),
|
||||
characteristic = message.characteristic,
|
||||
value = message.value,
|
||||
)
|
||||
// save the campaign to the disk + update the flow.
|
||||
store.save(
|
||||
campaign = campaign.value.copy(characters = characters)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
|
||||
import com.pixelized.shared.lwa.campaignPath
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
class CampaignStore(
|
||||
private val factory: CampaignJsonFactory,
|
||||
private val json: Json,
|
||||
) {
|
||||
private val directory = File(campaignPath()).also { it.mkdirs() }
|
||||
private val flow = MutableStateFlow(value = Campaign.EMPTY)
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
scope.launch {
|
||||
flow.value = load()
|
||||
}
|
||||
}
|
||||
|
||||
fun campaignFlow(): StateFlow<Campaign> = flow
|
||||
|
||||
suspend fun load(): Campaign {
|
||||
return try {
|
||||
val json = file().readText(charset = Charsets.UTF_8)
|
||||
if (json.isBlank()) Campaign.EMPTY
|
||||
val campaign = this.json.decodeFromString<CampaignJson>(json)
|
||||
factory.convertFromJson(campaign)
|
||||
} catch (exception: Exception) {
|
||||
Campaign.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(campaign: Campaign) {
|
||||
// convert the data to json format
|
||||
val json = try {
|
||||
factory.convertToJson(data = campaign).let(json::encodeToString)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
// write the file
|
||||
try {
|
||||
val file = file()
|
||||
file.writeText(
|
||||
text = json,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
flow.value = campaign
|
||||
}
|
||||
|
||||
sealed class CampaignStoreException(root: Exception) : Exception(root)
|
||||
class JsonConversionException(root: Exception) : CampaignStoreException(root)
|
||||
class FileWriteException(root: Exception) : CampaignStoreException(root)
|
||||
|
||||
private fun file(): File {
|
||||
return File("${campaignPath()}campaign.json")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
|
||||
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CharacterSheetRepository(
|
||||
store: CharacterSheetStore,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private val sheets = store.characterSheetFlow()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun characterSheetFlow(): StateFlow<List<CharacterSheet>> {
|
||||
return sheets
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CharacterSheetService(
|
||||
private val store: CharacterSheetStore,
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
private val useCase: CharacterSheetUseCase,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
private val sheets = store.characterSheetFlow().stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun character(): List<CharacterSheetJson> {
|
||||
return sheets.value.map(factory::convertToJson)
|
||||
}
|
||||
|
||||
fun characterSkillChange(
|
||||
characterId: String,
|
||||
skillId: String,
|
||||
) {
|
||||
val character = sheets.value.firstOrNull { it.id == characterId }
|
||||
if (character != null) {
|
||||
val update = useCase.updateSkillUsage(
|
||||
character = character,
|
||||
skillId = skillId,
|
||||
)
|
||||
store.save(sheet = update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
|
||||
import com.pixelized.shared.lwa.characterStorePath
|
||||
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -17,9 +17,9 @@ import java.text.Collator
|
|||
|
||||
class CharacterSheetStore(
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
private val jsonFormatter: Json,
|
||||
private val json: Json,
|
||||
) {
|
||||
private val characterDirectory = File(characterStorePath()).also { it.mkdirs() }
|
||||
private val directory = File(characterStorePath()).also { it.mkdirs() }
|
||||
private val flow = MutableStateFlow<List<CharacterSheet>>(value = emptyList())
|
||||
|
||||
init {
|
||||
|
|
@ -39,7 +39,7 @@ class CharacterSheetStore(
|
|||
fun save(sheet: CharacterSheet) {
|
||||
// convert the character sheet into json format.
|
||||
val json = try {
|
||||
factory.convertToJson(sheet = sheet).let(jsonFormatter::encodeToString)
|
||||
factory.convertToJson(sheet = sheet).let(json::encodeToString)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ class CharacterSheetStore(
|
|||
JsonConversionException::class,
|
||||
)
|
||||
suspend fun load(): List<CharacterSheet> {
|
||||
return characterDirectory
|
||||
return directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
val json = try {
|
||||
|
|
@ -98,7 +98,7 @@ class CharacterSheetStore(
|
|||
return@mapNotNull null
|
||||
}
|
||||
try {
|
||||
val sheet = jsonFormatter.decodeFromString<CharacterSheetJson>(json)
|
||||
val sheet = this.json.decodeFromString<CharacterSheetJson>(json)
|
||||
factory.convertFromJson(sheet)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.pixelized.server.lwa.server
|
||||
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.shared.lwa.protocol.Message
|
||||
import com.pixelized.shared.lwa.protocol.MessageType
|
||||
import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage
|
||||
import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class Engine(
|
||||
private val characterService: CharacterSheetService,
|
||||
private val campaignService: CampaignService,
|
||||
private val json: Json,
|
||||
) {
|
||||
suspend fun handle(message: Message) {
|
||||
println(message)
|
||||
|
||||
when (message.type) {
|
||||
MessageType.Roll -> {
|
||||
Unit // Nothing to do here.
|
||||
}
|
||||
|
||||
MessageType.UpdateSkillUsage -> {
|
||||
val data: UpdateSkillUsageMessage = json.decodeFromString(message.value)
|
||||
characterService.characterSkillChange(
|
||||
characterId = data.characterId,
|
||||
skillId = data.skillId
|
||||
)
|
||||
}
|
||||
|
||||
MessageType.UpdatePlayerCharacteristic -> {
|
||||
val data: UpdatePlayerCharacteristicMessage = json.decodeFromString(message.value)
|
||||
campaignService.update(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ package com.pixelized.server.lwa.server
|
|||
|
||||
import com.pixelized.server.lwa.extention.decodeFromFrame
|
||||
import com.pixelized.server.lwa.extention.encodeToFrame
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetRepository
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.shared.lwa.SERVER_PORT
|
||||
import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.Message
|
||||
import com.pixelized.shared.lwa.sharedModuleDependencies
|
||||
import io.ktor.http.ContentType
|
||||
|
|
@ -65,24 +65,34 @@ class LocalServer {
|
|||
masking = false
|
||||
}
|
||||
|
||||
val repository by inject<CharacterSheetRepository>()
|
||||
val factory by inject<CharacterSheetJsonFactory>()
|
||||
val engine by inject<Engine>()
|
||||
val characterService by inject<CharacterSheetService>()
|
||||
val campaignService by inject<CampaignService>()
|
||||
|
||||
routing {
|
||||
get(
|
||||
path = "/",
|
||||
body = {
|
||||
call.respondText(contentType = ContentType.Text.Html) {
|
||||
"<a href=\"http://127.0.0.1:16030/characters\">characters</a>"
|
||||
"""<html><body><ul>
|
||||
<li><a href="http://127.0.0.1:16030/characters">characters</a></li>
|
||||
<li><a href="http://127.0.0.1:16030/campaign">campaign</a></li>
|
||||
</ul></body></html>"""
|
||||
}
|
||||
}
|
||||
)
|
||||
get(
|
||||
path = "/characters",
|
||||
body = {
|
||||
val body = repository.characterSheetFlow().value.map(factory::convertToJson)
|
||||
call.respond(body)
|
||||
call.respond(characterService.character())
|
||||
},
|
||||
)
|
||||
get(
|
||||
path = "/campaign",
|
||||
body = {
|
||||
call.respond(campaignService.campaign())
|
||||
}
|
||||
)
|
||||
webSocket(
|
||||
path = "/ws",
|
||||
handler = {
|
||||
|
|
@ -97,7 +107,8 @@ class LocalServer {
|
|||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
println(message)
|
||||
// log the message
|
||||
engine.handle(message)
|
||||
// broadcast to clients the message
|
||||
outgoingMessageBuffer.emit(message)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue