Add visibility toggle for player & npc + refactor WebSocketMessage.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-03-19 15:19:31 +01:00
parent 2d164b265e
commit 4c37d8b937
47 changed files with 475 additions and 304 deletions

View file

@ -6,8 +6,8 @@ import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.protocol.websocket.ToggleActiveAlteration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -88,15 +88,17 @@ class AlterationStore(
)
}
private suspend fun handleMessage(message: Message) {
when (val payload = message.value) {
is RestSynchronisation.ToggleActiveAlteration -> {
private suspend fun handleMessage(message: SocketMessage) {
when (message) {
is ToggleActiveAlteration -> {
setActiveAlteration(
characterInstanceId = campaignJsonFactory.characterInstanceIdFromJson(
characterInstanceIdJson = payload.characterId,
characterInstanceId = CharacterInstance.Id(
prefix = message.prefix,
characterSheetId = message.characterSheetId,
instanceId = message.instanceId,
),
alterationId = payload.alterationId,
active = payload.active,
alterationId = message.alterationId,
active = message.active,
)
}

View file

@ -6,9 +6,10 @@ import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.character
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.model.campaign.npc
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import com.pixelized.shared.lwa.usecase.CampaignUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -106,28 +107,46 @@ class CampaignStore(
// region : WebSocket message Handling.
private suspend fun handleMessage(message: Message) {
when (val payload = message.value) {
private suspend fun handleMessage(message: SocketMessage) {
when (message) {
is RestSynchronisation.Campaign -> {
campaign(update = true)
}
is CampaignMessage -> {
val instanceId = Campaign.CharacterInstance.Id(
prefix = payload.prefix,
characterSheetId = payload.characterSheetId,
instanceId = payload.instanceId,
prefix = message.prefix,
characterSheetId = message.characterSheetId,
instanceId = message.instanceId,
)
when (payload) {
when (message) {
is CampaignMessage.UpdateCharacteristic -> updateCharacteristic(
characterInstanceId = instanceId,
characteristic = factory.convertFromJson(json = payload.characteristic),
value = payload.value,
characteristic = factory.convertFromJson(json = message.characteristic),
value = message.value,
)
is CampaignMessage.UpdateDiminished -> updateDiminished(
characterInstanceId = instanceId,
diminished = payload.diminished,
diminished = message.diminished,
)
}
}
is GameMasterEvent -> when (message) {
is GameMasterEvent.ToggleNpc -> {
_campaignFlow.value = _campaignFlow.value.copy(
options = _campaignFlow.value.options.copy(
showNpcs = _campaignFlow.value.options.showNpcs.not()
)
)
}
is GameMasterEvent.TogglePlayer -> {
_campaignFlow.value = _campaignFlow.value.copy(
options = _campaignFlow.value.options.copy(
showParty = _campaignFlow.value.options.showParty.not()
)
)
}
}

View file

@ -5,9 +5,9 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -97,29 +97,29 @@ class CharacterSheetStore(
// endregion
// region: WebSocket & data update.
private suspend fun handleMessage(message: Message) {
when (val payload = message.value) {
is RestSynchronisation.CharacterUpdate -> {
private suspend fun handleMessage(message: SocketMessage) {
when (message) {
is RestSynchronisation.CharacterSheetUpdate -> {
getCharacterSheet(
characterSheetId = payload.id,
characterSheetId = message.characterSheetId,
forceUpdate = true,
)
if (_previewFlow.value.firstOrNull { it.characterSheetId == payload.id } == null) {
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
charactersPreview()
}
}
is RestSynchronisation.CharacterDelete -> {
is RestSynchronisation.CharacterSheetDelete -> {
_previewFlow.value = previewFlow.value.toMutableList()
.also { sheets -> sheets.removeIf { it.characterSheetId == payload.characterId } }
_detailFlow.delete(payload.characterId)
.also { sheets -> sheets.removeIf { it.characterSheetId == message.characterSheetId } }
_detailFlow.delete(message.characterSheetId)
}
is UpdateSkillUsageMessage -> {
updateCharacterSkillChange(
characterId = payload.characterSheetId,
skillId = payload.skillId,
used = payload.used,
characterId = message.characterSheetId,
skillId = message.skillId,
used = message.used,
)
}

View file

@ -2,8 +2,7 @@ package com.pixelized.desktop.lwa.repository.network
import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.MessagePayload
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
@ -20,7 +19,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
class NetworkRepository(
@ -31,9 +29,9 @@ class NetworkRepository(
private var networkJob: Job? = null
private var client: HttpClient? = null
private val outgoingMessageBuffer = MutableSharedFlow<Message>()
private val incomingMessageBuffer = MutableSharedFlow<Message>()
val data: SharedFlow<Message> get() = incomingMessageBuffer
private val outgoingMessageBuffer = MutableSharedFlow<SocketMessage>()
private val incomingMessageBuffer = MutableSharedFlow<SocketMessage>()
val data: SharedFlow<SocketMessage> get() = incomingMessageBuffer
private val _status = MutableStateFlow(Status.DISCONNECTED)
val status: StateFlow<Status> get() = _status
@ -67,7 +65,7 @@ class NetworkRepository(
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
val data = frame.readText()
val message = json.decodeFromString<Message>(data)
val message = json.decodeFromString<SocketMessage>(data)
incomingMessageBuffer.emit(message)
}
}
@ -96,14 +94,9 @@ class NetworkRepository(
}
suspend fun share(
playerName: String = settingsRepository.settings().playerName,
payload: MessagePayload,
message: SocketMessage,
) {
if (status.value == Status.CONNECTED) {
val message = Message(
from = playerName,
value = payload,
)
// emit the message into the outgoing buffer
outgoingMessageBuffer.emit(message)
}

View file

@ -1,21 +1,23 @@
package com.pixelized.desktop.lwa.repository.roll_history
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.RollMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
class RollHistoryRepository(
private val network: NetworkRepository,
network: NetworkRepository,
) {
private val scope = CoroutineScope(Dispatchers.IO)
val rolls: SharedFlow<RollMessage> = network.data
.mapNotNull { it.value as? RollMessage }
.filterIsInstance(RollMessage::class)
.shareIn(
scope = scope,
started = SharingStarted.Eagerly,

View file

@ -13,7 +13,7 @@ import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
class CharacterDetailCharacteristicDialogViewModel(
private val characterSheetRepository: CharacterSheetRepository,
@ -79,7 +79,8 @@ class CharacterDetailCharacteristicDialogViewModel(
)
// share the data through the websocket.
network.share(
payload = CampaignMessage.UpdateCharacteristic(
message = CampaignMessage.UpdateCharacteristic(
timestamp = System.currentTimeMillis(),
prefix = characterInstanceId.prefix,
characterSheetId = characterInstanceId.characterSheetId,
instanceId = characterInstanceId.instanceId,

View file

@ -13,7 +13,7 @@ import com.pixelized.desktop.lwa.ui.overlay.roll.DifficultyUio.Difficulty
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction.RollActionUio
import com.pixelized.shared.lwa.model.AlteredCharacterSheet
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.RollMessage
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
import com.pixelized.shared.lwa.usecase.SkillStepUseCase
import kotlinx.coroutines.Job
@ -31,6 +31,7 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__failure
import lwacharactersheet.composeapp.generated.resources.roll_page__special_success
import lwacharactersheet.composeapp.generated.resources.roll_page__success
import org.jetbrains.compose.resources.getString
import java.util.UUID
class RollViewModel(
private val characterSheetRepository: CharacterSheetRepository,
@ -267,7 +268,8 @@ class RollViewModel(
val rollAction = rollAction ?: return
val payload = RollMessage(
id = RollMessage.RollId.create(),
timestamp = System.currentTimeMillis(),
uuid = UUID.randomUUID().toString(),
prefix = rollAction.characterInstanceId.prefix,
characterSheetId = rollAction.characterInstanceId.characterSheetId,
instanceId = rollAction.characterInstanceId.instanceId,
@ -294,7 +296,7 @@ class RollViewModel(
},
)
networkRepository.share(
payload = payload,
message = payload,
)
}
}

View file

@ -7,7 +7,7 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.protocol.websocket.UpdateSkillUsageMessage
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -84,7 +84,8 @@ class CharacterDetailViewModel(
) {
val characterSheetId = displayedCharacterId.value?.characterSheetId ?: return
network.share(
payload = UpdateSkillUsageMessage(
message = UpdateSkillUsageMessage(
timestamp = System.currentTimeMillis(),
characterSheetId = characterSheetId,
skillId = skillId,
used = used.not(),

View file

@ -9,7 +9,7 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label
import org.jetbrains.compose.resources.getString
@ -56,7 +56,8 @@ class CharacterDiminishedViewModel(
) {
val diminished = dialog.value().text.toIntOrNull() ?: 0
networkRepository.share(
payload = CampaignMessage.UpdateDiminished(
message = CampaignMessage.UpdateDiminished(
timestamp = System.currentTimeMillis(),
prefix = dialog.characterInstanceId.prefix,
characterSheetId = dialog.characterInstanceId.characterSheetId,
instanceId = dialog.characterInstanceId.instanceId,

View file

@ -22,7 +22,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.stateIn
@ -53,7 +53,7 @@ abstract class CharacterRibbonViewModel(
*/
@OptIn(ExperimentalCoroutinesApi::class)
val characters: StateFlow<List<CharacterPortraitUio>> = campaignRepository.campaignFlow
.flatMapMerge { campaign ->
.flatMapLatest { campaign ->
when (campaign.data.isEmpty()) {
true -> flowOf(emptyList())
else -> combine<CharacterPortraitUio?, List<CharacterPortraitUio>>(

View file

@ -24,7 +24,7 @@ class NpcRibbonViewModel(
campaignRepository = campaignRepository,
ribbonFactory = ribbonFactory,
) {
override val Campaign.data get() = npcs
override val Campaign.data get() = if (options.showNpcs) npcs else emptyMap()
override val enableCharacterSheet = false
override val enableCharacterStats = false

View file

@ -24,7 +24,7 @@ class PlayerRibbonViewModel(
campaignRepository = campaignRepository,
ribbonFactory = ribbonFactory,
) {
override val Campaign.data get() = characters
override val Campaign.data get() = if (options.showParty) characters else emptyMap()
override val enableCharacterSheet = true
override val enableCharacterStats = true

View file

@ -10,11 +10,13 @@ import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.ToggleActiveAlteration
import com.pixelized.shared.lwa.protocol.websocket.UpdateSkillUsageMessage
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.chat__characteristic__hp
import lwacharactersheet.composeapp.generated.resources.chat__characteristic__pp
@ -30,27 +32,27 @@ class TextMessageFactory(
private val formatTime = SimpleDateFormat("HH:mm:ss")
suspend fun convertToTextMessage(
message: Message,
message: SocketMessage,
): TextMessage? {
val time = System.currentTimeMillis()
val time = message.timestamp
val id = formatId.format(time)
return when (val payload = message.value) {
return when (message) {
is RollMessage -> {
val sheetPreview = characterSheetRepository
.characterPreview(characterId = payload.characterSheetId)
.characterPreview(characterId = message.characterSheetId)
?: return null
RollTextMessageUio(
id = "${payload.id.rollId}-${payload.id.timestamp}",
id = "${message.uuid}-${message.timestamp}",
timestamp = formatTime.format(time),
character = sheetPreview.name,
skillLabel = payload.skillLabel,
rollDifficulty = payload.rollDifficulty,
rollValue = payload.rollValue,
rollSuccessLimit = payload.rollSuccessLimit,
resultLabel = payload.resultLabel,
resultType = when (payload.critical) {
skillLabel = message.skillLabel,
rollDifficulty = message.rollDifficulty,
rollValue = message.rollValue,
rollSuccessLimit = message.rollSuccessLimit,
resultLabel = message.resultLabel,
resultType = when (message.critical) {
RollMessage.Critical.CRITICAL_SUCCESS -> RollTextMessageUio.Critical.CRITICAL_SUCCESS
RollMessage.Critical.SPECIAL_SUCCESS -> RollTextMessageUio.Critical.SPECIAL_SUCCESS
RollMessage.Critical.SUCCESS -> RollTextMessageUio.Critical.SUCCESS
@ -63,25 +65,25 @@ class TextMessageFactory(
is CampaignMessage.UpdateDiminished -> {
val sheetPreview = characterSheetRepository
.characterPreview(characterId = payload.characterSheetId)
.characterPreview(characterId = message.characterSheetId)
?: return null
DiminishedTextMessageUio(
id = "${message.from}-$id-Diminished",
id = "${message.timestamp}-$id-Diminished",
timestamp = formatTime.format(time),
character = sheetPreview.name,
diminished = payload.diminished,
diminished = message.diminished,
)
}
is CampaignMessage.UpdateCharacteristic -> {
val sheet = characterSheetRepository.characterDetail(
characterSheetId = payload.characterSheetId,
characterSheetId = message.characterSheetId,
) ?: return null
val characterInstanceId = Campaign.CharacterInstance.Id(
prefix = payload.prefix,
characterSheetId = payload.characterSheetId,
instanceId = payload.instanceId,
prefix = message.prefix,
characterSheetId = message.characterSheetId,
instanceId = message.instanceId,
)
val alterations = alterationRepository.alterations(
characterInstanceId = characterInstanceId,
@ -91,25 +93,26 @@ class TextMessageFactory(
alterations = alterations,
)
CharacteristicTextMessageUio(
id = "${message.from}-$id-Characteristic",
id = "${message.timestamp}-$id-Characteristic",
timestamp = formatTime.format(time),
character = sheet.name,
value = when (payload.characteristic) {
Damage -> alteredSheet.maxHp - payload.value
Power -> alteredSheet.maxPp - payload.value
value = when (message.characteristic) {
Damage -> alteredSheet.maxHp - message.value
Power -> alteredSheet.maxPp - message.value
},
characteristic = when (payload.characteristic) {
characteristic = when (message.characteristic) {
Damage -> getString(Res.string.chat__characteristic__hp)
Power -> getString(Res.string.chat__characteristic__pp)
},
)
}
RestSynchronisation.Campaign -> null
is RestSynchronisation.CharacterDelete -> null
is RestSynchronisation.CharacterUpdate -> null
is RestSynchronisation.ToggleActiveAlteration -> null
is RestSynchronisation.Campaign -> null
is RestSynchronisation.CharacterSheetDelete -> null
is RestSynchronisation.CharacterSheetUpdate -> null
is ToggleActiveAlteration -> null
is UpdateSkillUsageMessage -> null
is GameMasterEvent -> null
}
}
}

View file

@ -14,8 +14,8 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.UpdateSkillUsageMessage
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@ -80,7 +80,8 @@ class CharacterSheetViewModel(
fun onUseSkill(skill: CharacterSheetPageUio.Node) {
viewModelScope.launch {
network.share(
payload = UpdateSkillUsageMessage(
message = UpdateSkillUsageMessage(
timestamp = System.currentTimeMillis(),
characterSheetId = argument.characterInstanceId.characterSheetId,
skillId = skill.id,
used = skill.used.not(),
@ -143,7 +144,8 @@ class CharacterSheetViewModel(
suspend fun changeDiminished(dialog: DiminishedStatDialogUio) {
val diminished = dialog.value().text.toIntOrNull() ?: 0
network.share(
payload = CampaignMessage.UpdateDiminished(
message = CampaignMessage.UpdateDiminished(
timestamp = System.currentTimeMillis(),
prefix = dialog.characterInstanceId.prefix,
characterSheetId = dialog.characterInstanceId.characterSheetId,
instanceId = dialog.characterInstanceId.instanceId,

View file

@ -29,9 +29,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Switch
import androidx.compose.material.SwitchColors
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@ -43,7 +41,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalWindowController
@ -63,6 +60,8 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__ed
import lwacharactersheet.composeapp.generated.resources.game_master__action
import lwacharactersheet.composeapp.generated.resources.game_master__title
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@ -77,6 +76,7 @@ fun GameMasterScreen(
val characters = viewModel.characters.collectAsState()
val gameMaster = viewModel.gameMaster.collectAsState()
val npcVisibility = viewModel.npcVisibility.collectAsState()
val tags = viewModel.tags.collectAsState()
Surface(
@ -87,6 +87,7 @@ fun GameMasterScreen(
filter = viewModel.filter,
tags = tags,
gameMaster = gameMaster,
npcVisibility = npcVisibility,
characters = characters,
onTag = viewModel::onTag,
onGameMaster = viewModel::onGameMaster,
@ -107,6 +108,11 @@ fun GameMasterScreen(
)
}
},
onNpcVisibility = {
scope.launch {
viewModel.onNpcVisibility()
}
},
)
}
}
@ -120,16 +126,18 @@ private fun GameMasterContent(
filter: LwaTextFieldUio,
tags: State<List<GMTagUio>>,
gameMaster: State<Boolean>,
npcVisibility: State<Boolean>,
characters: State<List<GMCharacterUio>>,
onGameMaster: (Boolean) -> Unit,
onTag: (GMTagUio.TagId) -> Unit,
onCharacterAction: (String, GMCharacterUio.Action) -> Unit,
onCharacterSheetEdit: (String) -> Unit,
onCharacterSheetCreate: () -> Unit,
onNpcVisibility: () -> Unit,
) {
val scope = rememberCoroutineScope()
Scaffold(
GameMasterLayout(
modifier = modifier,
topBar = {
TopAppBar(
@ -161,10 +169,8 @@ private fun GameMasterContent(
}
)
},
content = { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues = paddingValues)
) {
content = {
Column {
Surface(
elevation = 1.dp,
) {
@ -210,7 +216,6 @@ private fun GameMasterContent(
items = tags.value,
) { tag ->
GMTag(
style = MaterialTheme.lwa.typography.base.caption,
tag = tag,
onTag = { onTag(tag.id) },
)
@ -219,7 +224,9 @@ private fun GameMasterContent(
}
}
Box(
modifier = Modifier.fillMaxWidth().weight(1f),
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
LazyColumn(
modifier = Modifier.matchParentSize(),
@ -251,22 +258,71 @@ private fun GameMasterContent(
)
}
}
IconButton(
modifier = Modifier
.align(alignment = Alignment.BottomEnd)
.padding(all = padding)
.background(
color = MaterialTheme.lwa.colorScheme.base.primary,
shape = CircleShape,
),
onClick = onCharacterSheetCreate,
) {
Icon(
imageVector = Icons.Default.Add,
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
contentDescription = null,
)
}
}
}
},
fab = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(all = padding),
horizontalArrangement = Arrangement.SpaceBetween,
) {
IconButton(
modifier = Modifier.background(
color = MaterialTheme.lwa.colorScheme.base.primary,
shape = CircleShape,
),
onClick = onNpcVisibility,
) {
Icon(
painter = when (npcVisibility.value) {
true -> painterResource(Res.drawable.ic_visibility_off_24dp)
else -> painterResource(Res.drawable.ic_visibility_24dp)
},
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
contentDescription = null,
)
}
IconButton(
modifier = Modifier.background(
color = MaterialTheme.lwa.colorScheme.base.primary,
shape = CircleShape,
),
onClick = onCharacterSheetCreate,
) {
Icon(
imageVector = Icons.Default.Add,
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
contentDescription = null,
)
}
}
}
)
}
@Composable
private fun GameMasterLayout(
modifier: Modifier,
topBar: @Composable () -> Unit,
content: @Composable () -> Unit,
fab: @Composable () -> Unit,
) {
Scaffold(
modifier = modifier,
topBar = topBar,
content = { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues = paddingValues)
) {
content()
Row(
modifier = Modifier.align(alignment = Alignment.BottomStart),
) {
fab()
}
}
}

View file

@ -4,11 +4,13 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio.TagId
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
@ -26,6 +28,7 @@ class GameMasterViewModel(
campaignRepository: CampaignRepository,
characterSheetRepository: CharacterSheetRepository,
private val settingsRepository: SettingsRepository,
private val networkRepository: NetworkRepository,
private val factory: GameMasterFactory,
private val useCase: GameMasterActionUseCase,
) : ViewModel() {
@ -82,6 +85,14 @@ class GameMasterViewModel(
initialValue = false,
)
val npcVisibility = campaignRepository.campaignFlow
.map { it.options.showNpcs }
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
fun onGameMaster(value: Boolean) {
val settings = settingsRepository.settings()
settingsRepository.update(
@ -114,4 +125,12 @@ class GameMasterViewModel(
it[id] = it.getOrPut(id) { true }.not()
}
}
suspend fun onNpcVisibility() {
networkRepository.share(
GameMasterEvent.ToggleNpc(
timestamp = System.currentTimeMillis(),
)
)
}
}

View file

@ -144,7 +144,6 @@ fun GMCharacter(
) {
character.tags.forEach { tag ->
GMTag(
style = MaterialTheme.lwa.typography.base.caption,
elevation = 4.dp,
tag = tag,
)

View file

@ -41,7 +41,6 @@ fun GMTag(
padding: PaddingValues = GmTagDefault.padding,
shape: Shape = CircleShape,
elevation: Dp = 2.dp,
style: TextStyle,
tag: GMTagUio,
onTag: (() -> Unit)? = null,
) {
@ -60,7 +59,7 @@ fun GMTag(
modifier = Modifier
.clickable(enabled = onTag != null) { onTag?.invoke() }
.padding(paddingValues = padding),
style = style,
style = MaterialTheme.lwa.typography.base.caption,
color = animatedColor.value,
text = tag.label,
)

View file

@ -209,4 +209,24 @@ class CampaignService(
}
}
}
suspend fun updateToggleParty() {
store.save(
campaign = campaign.copy(
options = campaign.options.copy(
showParty = campaign.options.showParty.not()
)
)
)
}
suspend fun updateToggleNpc() {
store.save(
campaign = campaign.copy(
options = campaign.options.copy(
showNpcs = campaign.options.showNpcs.not()
)
)
)
}
}

View file

@ -5,11 +5,13 @@ import com.pixelized.server.lwa.model.campaign.CampaignService
import com.pixelized.server.lwa.model.character.CharacterSheetService
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.protocol.websocket.CampaignMessage
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RollMessage
import com.pixelized.shared.lwa.protocol.websocket.ToggleActiveAlteration
import com.pixelized.shared.lwa.protocol.websocket.UpdateSkillUsageMessage
import kotlinx.coroutines.flow.MutableSharedFlow
class Engine(
@ -18,46 +20,51 @@ class Engine(
val alterationService: AlterationService,
val campaignJsonFactory: CampaignJsonFactory,
) {
val webSocket = MutableSharedFlow<Message>()
val webSocket = MutableSharedFlow<SocketMessage>()
suspend fun handle(message: Message) {
when (val data = message.value) {
suspend fun handle(message: SocketMessage) {
when (message) {
is RollMessage -> Unit // Nothing to do here.
is CampaignMessage -> {
val instanceId = Campaign.CharacterInstance.Id(
prefix = data.prefix,
characterSheetId = data.characterSheetId,
instanceId = data.instanceId,
prefix = message.prefix,
characterSheetId = message.characterSheetId,
instanceId = message.instanceId,
)
when (data) {
when (message) {
is CampaignMessage.UpdateCharacteristic -> campaignService.updateCharacteristic(
characterInstanceId = instanceId,
characteristic = campaignJsonFactory.convertFromJson(json = data.characteristic),
value = data.value,
characteristic = campaignJsonFactory.convertFromJson(json = message.characteristic),
value = message.value,
)
is CampaignMessage.UpdateDiminished -> campaignService.updateDiminished(
characterInstanceId = instanceId,
diminished = data.diminished,
diminished = message.diminished,
)
}
}
is UpdateSkillUsageMessage -> characterService.updateCharacterSkillUsage(
characterSheetId = data.characterSheetId,
skillId = data.skillId,
used = data.used,
characterSheetId = message.characterSheetId,
skillId = message.skillId,
used = message.used,
)
RestSynchronisation.Campaign -> Unit // Handle in the Rest
is RestSynchronisation.Campaign -> Unit // Handle in the Rest
is RestSynchronisation.CharacterUpdate -> Unit // Handle in the Rest
is RestSynchronisation.CharacterSheetUpdate -> Unit // Handle in the Rest
is RestSynchronisation.CharacterDelete -> Unit // Handle in the Rest
is RestSynchronisation.CharacterSheetDelete -> Unit // Handle in the Rest
is RestSynchronisation.ToggleActiveAlteration -> Unit // Handle in the Rest
is ToggleActiveAlteration -> Unit // Handle in the Rest
is GameMasterEvent -> when (message) {
is GameMasterEvent.TogglePlayer -> campaignService.updateToggleParty()
is GameMasterEvent.ToggleNpc -> campaignService.updateToggleNpc()
}
}
}
}

View file

@ -15,7 +15,7 @@ import com.pixelized.server.lwa.server.rest.character.getCharacter
import com.pixelized.server.lwa.server.rest.character.getCharacters
import com.pixelized.server.lwa.server.rest.character.putCharacter
import com.pixelized.shared.lwa.SERVER_PORT
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import com.pixelized.shared.lwa.sharedModuleDependencies
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.install
@ -97,7 +97,7 @@ class LocalServer {
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
val data = frame.readText()
val message = json.decodeFromString<Message>(data)
val message = json.decodeFromString<SocketMessage>(data)
// log the message
engine.handle(message)
// broadcast to clients the message

View file

@ -2,8 +2,7 @@ package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.ToggleActiveAlteration
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
import io.ktor.server.response.respondText
@ -32,15 +31,15 @@ fun Engine.putActiveAlteration(): suspend io.ktor.server.routing.RoutingContext.
)
// share the modification to all client through the websocket.
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.ToggleActiveAlteration(
characterId = campaignJsonFactory.convertToJson(id = characterInstanceId),
alterationId = alterationId,
active = alterationService.isAlterationActive(
characterInstanceId = characterInstanceId,
alterationId = alterationId
),
ToggleActiveAlteration(
timestamp = System.currentTimeMillis(),
prefix = characterInstanceId.prefix,
characterSheetId = characterInstanceId.characterSheetId,
instanceId = characterInstanceId.instanceId,
alterationId = alterationId,
active = alterationService.isAlterationActive(
characterInstanceId = characterInstanceId,
alterationId = alterationId
),
)
)

View file

@ -2,8 +2,7 @@ package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
@ -24,9 +23,8 @@ fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingCont
status = HttpStatusCode.Accepted,
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.Campaign,
RestSynchronisation.Campaign(
timestamp = System.currentTimeMillis(),
)
)
} catch (exception: Exception) {

View file

@ -2,8 +2,7 @@ package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
@ -24,9 +23,8 @@ fun Engine.deleteCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.()
status = HttpStatusCode.Accepted,
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.Campaign,
RestSynchronisation.Campaign(
timestamp = System.currentTimeMillis(),
)
)
} catch (exception: Exception) {

View file

@ -1,11 +1,8 @@
package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
import io.ktor.server.response.respondText
@ -29,9 +26,8 @@ fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.()
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.Campaign,
RestSynchronisation.Campaign(
timestamp = System.currentTimeMillis(),
)
)
}

View file

@ -3,8 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
@ -36,9 +35,8 @@ fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext
status = HttpStatusCode.Accepted,
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.Campaign,
RestSynchronisation.Campaign(
timestamp = System.currentTimeMillis(),
)
)
} catch (exception: Exception) {

View file

@ -3,8 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
@ -39,9 +38,8 @@ fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() ->
status = HttpStatusCode.Accepted,
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.Campaign,
RestSynchronisation.Campaign(
timestamp = System.currentTimeMillis(),
)
)
} catch (exception: Exception) {

View file

@ -2,8 +2,7 @@ package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
@ -18,10 +17,10 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
status = HttpStatusCode.OK,
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.CharacterDelete(characterId = characterSheetId),
)
RestSynchronisation.CharacterSheetDelete(
timestamp = System.currentTimeMillis(),
characterSheetId = characterSheetId,
),
)
} else {
call.respondText(

View file

@ -2,8 +2,7 @@ package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.protocol.websocket.Message
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.RestSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
import io.ktor.server.response.respondText
@ -19,10 +18,10 @@ fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
status = HttpStatusCode.OK
)
webSocket.emit(
Message(
from = "Server",
value = RestSynchronisation.CharacterUpdate(id = form.id),
)
RestSynchronisation.CharacterSheetUpdate(
timestamp = System.currentTimeMillis(),
characterSheetId = form.id,
),
)
}
}

View file

@ -4,6 +4,7 @@ data class Campaign(
val characters: Map<CharacterInstance.Id, CharacterInstance>,
val npcs: Map<CharacterInstance.Id, CharacterInstance>,
val scene: Scene,
val options: Options,
) {
data class CharacterInstance(
val characteristic: Map<Characteristic, Int>,
@ -53,11 +54,24 @@ data class Campaign(
}
}
data class Options(
val showParty: Boolean,
val showNpcs: Boolean,
) {
companion object {
fun empty() = Options(
showParty = true,
showNpcs = false,
)
}
}
companion object {
val EMPTY = Campaign(
characters = emptyMap(),
npcs = emptyMap(),
scene = Scene.empty(),
options = Options.empty(),
)
}
}

View file

@ -14,4 +14,7 @@ sealed interface CampaignJson {
@Serializable
sealed interface SceneJson
@Serializable
sealed interface OptionJson
}

View file

@ -7,6 +7,7 @@ data class CampaignJsonV1(
val characters: Map<String, CharacterInstanceJsonV1>,
val npcs: Map<String, CharacterInstanceJsonV1>,
val scene: SceneJsonV1?,
val options: OptionsJsonV1?,
) : CampaignJson {
@Serializable
@ -24,4 +25,10 @@ data class CampaignJsonV1(
data class SceneJsonV1(
val name: String,
) : CampaignJson.SceneJson
@Serializable
data class OptionsJsonV1(
val showPlayer: Boolean,
val showNpcs: Boolean,
) : CampaignJson.OptionJson
}

View file

@ -58,6 +58,10 @@ class CampaignJsonFactory(
.toMap(),
scene = CampaignJsonV1.SceneJsonV1(
name = data.scene.name
),
options = CampaignJsonV1.OptionsJsonV1(
showPlayer = data.options.showParty,
showNpcs = data.options.showNpcs,
)
)
}

View file

@ -26,6 +26,9 @@ class CampaignJsonV1Factory {
scene = campaignJson.scene
?.let { convertFromV1(it) }
?: Campaign.Scene.empty(),
options = campaignJson.options
?.let { convertFromV1(it) }
?:Campaign.Options.empty()
)
}
@ -66,4 +69,13 @@ class CampaignJsonV1Factory {
name = sceneJson.name
)
}
fun convertFromV1(
optionsJson: CampaignJsonV1.OptionsJsonV1,
): Campaign.Options {
return Campaign.Options(
showParty = optionsJson.showPlayer,
showNpcs = optionsJson.showNpcs
)
}
}

View file

@ -1,16 +1,17 @@
package com.pixelized.shared.lwa.protocol.websocket.payload
package com.pixelized.shared.lwa.protocol.websocket
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1
import kotlinx.serialization.Serializable
@Serializable
sealed interface CampaignMessage : MessagePayload {
val prefix: Char
val characterSheetId: String
val instanceId: Int
sealed interface CampaignMessage : SocketMessage, CharacterInstanceIdMessage {
override val prefix: Char
override val characterSheetId: String
override val instanceId: Int
@Serializable
data class UpdateCharacteristic(
override val timestamp: Long,
override val prefix: Char,
override val characterSheetId: String,
override val instanceId: Int,
@ -20,6 +21,7 @@ sealed interface CampaignMessage : MessagePayload {
@Serializable
data class UpdateDiminished(
override val timestamp: Long,
override val prefix: Char,
override val characterSheetId: String,
override val instanceId: Int,

View file

@ -0,0 +1,15 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
sealed interface CharacterSheetIdMessage {
val characterSheetId: String
}
@Serializable
sealed interface CharacterInstanceIdMessage : CharacterSheetIdMessage {
override val characterSheetId: String
val prefix: Char
val instanceId: Int
}

View file

@ -0,0 +1,17 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
sealed interface GameMasterEvent : SocketMessage {
@Serializable
class ToggleNpc(
override val timestamp: Long,
) : GameMasterEvent
@Serializable
class TogglePlayer(
override val timestamp: Long,
) : GameMasterEvent
}

View file

@ -1,10 +0,0 @@
package com.pixelized.shared.lwa.protocol.websocket
import com.pixelized.shared.lwa.protocol.websocket.payload.MessagePayload
import kotlinx.serialization.Serializable
@Serializable
data class Message(
val from: String,
val value: MessagePayload,
)

View file

@ -0,0 +1,24 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
sealed class RestSynchronisation : SocketMessage {
@Serializable
data class CharacterSheetUpdate(
override val timestamp: Long,
override val characterSheetId: String,
) : RestSynchronisation(), CharacterSheetIdMessage
@Serializable
data class CharacterSheetDelete(
override val timestamp: Long,
override val characterSheetId: String,
) : RestSynchronisation(), CharacterSheetIdMessage
@Serializable
data class Campaign(
override val timestamp: Long,
) : RestSynchronisation()
}

View file

@ -0,0 +1,26 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
data class RollMessage(
override val timestamp: Long,
val uuid: String,
val prefix: Char,
val characterSheetId: String,
val instanceId: Int?,
val skillLabel: String,
val rollValue: Int,
val resultLabel: String? = null,
val rollDifficulty: String? = null,
val rollSuccessLimit: Int? = null,
val critical: Critical? = null,
) : SocketMessage {
enum class Critical {
CRITICAL_SUCCESS,
SPECIAL_SUCCESS,
SUCCESS,
FAILURE,
CRITICAL_FAILURE
}
}

View file

@ -0,0 +1,8 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
sealed interface SocketMessage {
val timestamp: Long
}

View file

@ -0,0 +1,13 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
data class ToggleActiveAlteration(
override val timestamp: Long,
override val characterSheetId: String,
override val prefix: Char,
override val instanceId: Int,
val alterationId: String,
val active: Boolean,
) : SocketMessage, CharacterInstanceIdMessage

View file

@ -0,0 +1,11 @@
package com.pixelized.shared.lwa.protocol.websocket
import kotlinx.serialization.Serializable
@Serializable
data class UpdateSkillUsageMessage(
override val timestamp: Long,
override val characterSheetId: String,
val skillId: String,
val used: Boolean,
) : SocketMessage, CharacterSheetIdMessage

View file

@ -1,6 +0,0 @@
package com.pixelized.shared.lwa.protocol.websocket.payload
import kotlinx.serialization.Serializable
@Serializable
sealed interface MessagePayload

View file

@ -1,27 +0,0 @@
package com.pixelized.shared.lwa.protocol.websocket.payload
import kotlinx.serialization.Serializable
@Serializable
sealed class RestSynchronisation : MessagePayload {
@Serializable
data class CharacterUpdate(
val id: String,
) : RestSynchronisation()
@Serializable
data class CharacterDelete(
val characterId: String,
) : RestSynchronisation()
@Serializable
data class ToggleActiveAlteration(
val characterId: String,
val alterationId: String,
val active: Boolean,
) : RestSynchronisation()
@Serializable
data object Campaign : RestSynchronisation()
}

View file

@ -1,43 +0,0 @@
package com.pixelized.shared.lwa.protocol.websocket.payload
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class RollMessage(
val id: RollId,
val prefix: Char,
val characterSheetId: String,
val instanceId: Int?,
val skillLabel: String,
val rollValue: Int,
val resultLabel: String? = null,
val rollDifficulty: String? = null,
val rollSuccessLimit: Int? = null,
val critical: Critical? = null,
) : MessagePayload {
@Serializable
data class RollId(
val rollId: String,
val timestamp: Long,
) {
companion object {
fun create(
rollId: String = UUID.randomUUID().toString(),
timestamp: Long = System.currentTimeMillis(),
) = RollId(
rollId = rollId,
timestamp = timestamp,
)
}
}
enum class Critical {
CRITICAL_SUCCESS,
SPECIAL_SUCCESS,
SUCCESS,
FAILURE,
CRITICAL_FAILURE
}
}

View file

@ -1,10 +0,0 @@
package com.pixelized.shared.lwa.protocol.websocket.payload
import kotlinx.serialization.Serializable
@Serializable
data class UpdateSkillUsageMessage(
val characterSheetId: String,
val skillId: String,
val used: Boolean,
) : MessagePayload