Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove non-character NPCs #178

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,13 @@ import cz.frantisekmasa.wfrp_master.common.core.firebase.functions.CloudFunction
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestoreCharacterItemRepository
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestoreCharacterRepository
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestoreEncounterRepository
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestoreNpcRepository
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestorePartyRepository
import cz.frantisekmasa.wfrp_master.common.core.firebase.repositories.FirestoreSkillRepository
import cz.frantisekmasa.wfrp_master.common.core.serialization.UuidSerializer
import cz.frantisekmasa.wfrp_master.common.core.tips.DismissedUserTipsHolder
import cz.frantisekmasa.wfrp_master.common.encounters.EncounterDetailScreenModel
import cz.frantisekmasa.wfrp_master.common.encounters.EncountersScreenModel
import cz.frantisekmasa.wfrp_master.common.encounters.domain.EncounterRepository
import cz.frantisekmasa.wfrp_master.common.encounters.domain.NpcRepository
import cz.frantisekmasa.wfrp_master.common.gameMaster.GameMasterScreenModel
import cz.frantisekmasa.wfrp_master.common.invitation.InvitationScreenModel
import cz.frantisekmasa.wfrp_master.common.invitation.domain.FirestoreInvitationProcessor
Expand Down Expand Up @@ -157,7 +155,6 @@ val appModule = DI.Module("Common") {
}

bindSingleton<EncounterRepository> { FirestoreEncounterRepository(instance(), mapper()) }
bindSingleton<NpcRepository> { FirestoreNpcRepository(instance(), mapper()) }

bindSingleton<CharacterAvatarChanger> { CloudFunctionCharacterAvatarChanger(instance()) }

Expand Down Expand Up @@ -196,7 +193,7 @@ val appModule = DI.Module("Common") {
bindFactory { partyId: PartyId -> EncountersScreenModel(partyId, instance()) }
bindFactory { partyId: PartyId -> PartyScreenModel(partyId, instance()) }
bindFactory { encounterId: EncounterId ->
EncounterDetailScreenModel(encounterId, instance(), instance(), instance(), instance())
EncounterDetailScreenModel(encounterId, instance(), instance(), instance())
}
bindFactory { characterId: CharacterId ->
SkillsScreenModel(characterId, instance(), instance(), instance(), instance())
Expand Down Expand Up @@ -316,7 +313,6 @@ val appModule = DI.Module("Common") {
instance(),
instance(),
instance(),
instance(),
)
}
bindFactory { partyId: PartyId -> PartySettingsScreenModel(partyId, instance()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyId
import cz.frantisekmasa.wfrp_master.common.core.domain.party.combat.Advantage
import cz.frantisekmasa.wfrp_master.common.core.domain.party.combat.GroupAdvantage
import cz.frantisekmasa.wfrp_master.common.core.domain.party.settings.AdvantageSystem
import cz.frantisekmasa.wfrp_master.common.core.shared.Resources
import cz.frantisekmasa.wfrp_master.common.core.ui.CharacterAvatar
import cz.frantisekmasa.wfrp_master.common.core.ui.StatBlock
import cz.frantisekmasa.wfrp_master.common.core.ui.StatBlockData
Expand All @@ -81,7 +80,6 @@ import cz.frantisekmasa.wfrp_master.common.core.ui.scaffolding.OptionsAction
import cz.frantisekmasa.wfrp_master.common.core.ui.scaffolding.Subtitle
import cz.frantisekmasa.wfrp_master.common.encounters.CombatantItem
import cz.frantisekmasa.wfrp_master.common.encounters.domain.Wounds
import cz.frantisekmasa.wfrp_master.common.npcs.NpcDetailScreen
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -152,13 +150,10 @@ class ActiveCombatScreen(
isGroupAdvantageSystemEnabled = isGroupAdvantageSystemEnabled,
onDetailOpenRequest = {
navigation.navigate(
when (freshCombatant) {
is CombatantItem.Npc -> NpcDetailScreen(freshCombatant.npcId)
is CombatantItem.Character -> CharacterDetailScreen(
freshCombatant.characterId,
comingFromCombat = true,
)
}
CharacterDetailScreen(
freshCombatant.characterId,
comingFromCombat = true,
)
)
coroutineScope.launch { bottomSheetState.hide() }
},
Expand Down Expand Up @@ -279,7 +274,7 @@ class ActiveCombatScreen(

@Stable
private fun canEditCombatant(userId: UserId, isGameMaster: Boolean, combatant: CombatantItem) =
isGameMaster || (combatant is CombatantItem.Character && combatant.userId == userId)
isGameMaster || combatant.userId == userId

@Composable
private fun GroupAdvantageBar(
Expand Down Expand Up @@ -593,16 +588,7 @@ class ActiveCombatScreen(

Column {
ListItem(
icon = {
when (combatant) {
is CombatantItem.Character -> {
CharacterAvatar(combatant.avatarUrl, ItemIcon.Size.Small)
}
is CombatantItem.Npc -> {
ItemIcon(Resources.Drawable.Npc, ItemIcon.Size.Small)
}
}
},
icon = { CharacterAvatar(combatant.avatarUrl, ItemIcon.Size.Small) },
text = {
Column {
Text(combatant.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterType
import cz.frantisekmasa.wfrp_master.common.core.domain.character.CurrentConditions
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.EncounterId
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.NpcId
import cz.frantisekmasa.wfrp_master.common.core.domain.party.Party
import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyId
import cz.frantisekmasa.wfrp_master.common.core.domain.party.PartyRepository
Expand All @@ -33,28 +32,23 @@ import cz.frantisekmasa.wfrp_master.common.core.logging.Reporter
import cz.frantisekmasa.wfrp_master.common.core.ui.StatBlockData
import cz.frantisekmasa.wfrp_master.common.core.utils.right
import cz.frantisekmasa.wfrp_master.common.encounters.CombatantItem
import cz.frantisekmasa.wfrp_master.common.encounters.domain.Npc
import cz.frantisekmasa.wfrp_master.common.encounters.domain.NpcRepository
import cz.frantisekmasa.wfrp_master.common.encounters.domain.Wounds
import io.github.aakira.napier.Napier
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.transform
import kotlin.random.Random

class CombatScreenModel(
private val partyId: PartyId,
private val random: Random,
private val parties: PartyRepository,
private val npcs: NpcRepository,
private val characters: CharacterRepository,
private val skills: SkillRepository,
private val talents: TalentRepository,
Expand All @@ -70,10 +64,6 @@ class CombatScreenModel(
.mapLatest { it.activeCombat }
.filterNotNull()

private val activeEncounterId: Flow<EncounterId> = combatFlow
.mapLatest { EncounterId(partyId, it.encounterId) }
.distinctUntilChanged()

val turn: Flow<Int> = combatFlow
.mapLatest { it.getTurn() }
.distinctUntilChanged()
Expand All @@ -86,28 +76,13 @@ class CombatScreenModel(
.mapLatest { it.groupAdvantage }
.distinctUntilChanged()

suspend fun loadNpcsFromEncounter(encounterId: Uuid): List<Npc> =
npcs.findByEncounter(EncounterId(partyId, encounterId)).first()

suspend fun loadCharacters(): List<Character> =
characters.inParty(partyId, CharacterType.PLAYER_CHARACTER).first()

suspend fun loadNpcs(): List<Character> =
characters.inParty(partyId, CharacterType.NPC).first()

suspend fun getStatBlockData(combatant: CombatantItem): StatBlockData {
if (combatant !is CombatantItem.Character) {
return StatBlockData(
"",
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
emptyList(),
)
}

val characterId = combatant.characterId

return coroutineScope {
Expand All @@ -133,39 +108,31 @@ class CombatScreenModel(
suspend fun startCombat(
encounterId: Uuid,
characters: List<Character>,
npcs: List<Npc>,
npcCharacters: Map<Character, Int>,
) {
val globalEncounterId = EncounterId(partyId, encounterId)
val combatants =
characters.map {
characteristics(it) to Combatant.Character(
characteristics(it) to Combatant(
id = uuid4(),
characterId = it.id,
initiative = 1,
)
} +
npcs.map {
it.stats to Combatant.Npc(
id = uuid4(),
npcId = NpcId(globalEncounterId, it.id),
initiative = 1,
)
} +
npcCharacters.flatMap { (character, count) ->
val characteristics = characteristics(character)

if (count == 1)
listOf(
characteristics to Combatant.Character(
characteristics to Combatant(
id = uuid4(),
name = character.publicName,
characterId = character.id,
initiative = 1,
)
)
else (1..count).map { index ->
characteristics to Combatant.Character(
characteristics to Combatant(
id = uuid4(),
characterId = character.id,
initiative = 1,
Expand Down Expand Up @@ -205,8 +172,6 @@ class CombatScreenModel(
}

fun combatants(): Flow<List<CombatantItem>> {
val npcsFlow = activeEncounterId.transform { emitAll(npcs.findByEncounter(it)) }

val charactersFlow = characters
.inParty(partyId, CharacterType.values().toSet())
.distinctUntilChanged()
Expand All @@ -215,36 +180,18 @@ class CombatScreenModel(
.mapNotNull { it.activeCombat?.getCombatants() }
.distinctUntilChanged()

return combineFlows(
combatantsFlow,
npcsFlow,
charactersFlow
) { combatants, npcs, characters ->
val npcsById = npcs.associateBy { it.id }
return combatantsFlow.combine(charactersFlow) { combatants, characters ->
val charactersById = characters.associateBy { it.id }

combatants
.map { combatant ->
when (combatant) {
is Combatant.Character -> {
val character = charactersById[combatant.characterId] ?: return@map null

CombatantItem.Character(
characterId = CharacterId(partyId, character.id),
character = character,
combatant = combatant,
)
}
is Combatant.Npc -> {
val npc = npcsById[combatant.npcId.npcId] ?: return@map null

CombatantItem.Npc(
npcId = combatant.npcId,
npc = npc,
combatant = combatant,
)
}
}
val character = charactersById[combatant.characterId] ?: return@map null

CombatantItem(
characterId = CharacterId(partyId, character.id),
character = character,
combatant = combatant,
)
}.filterNotNull()
}
}
Expand Down Expand Up @@ -282,43 +229,21 @@ class CombatScreenModel(
else party.updateCombat(updatedCombat)
}

private fun <T1, T2, T3, R> combineFlows(
first: Flow<T1>,
second: Flow<T2>,
third: Flow<T3>,
transform: suspend (T1, T2, T3) -> R,
): Flow<R> =
first.combine(second) { a, b -> Pair(a, b) }
.combine(third) { (a, b), c -> transform(a, b, c) }

suspend fun updateWounds(combatant: CombatantItem, wounds: Wounds) {
if (combatant.combatant.wounds != null) {
// Wounds are combatant specific (there may be multiple combatants of same character)
updateCombat { it.updateCombatant(combatant.combatant.withWounds(wounds)) }
return
}

when (combatant) {
is CombatantItem.Character -> {
val character = characters.get(combatant.characterId)
val points = character.points

if (points.wounds == wounds.current) {
return
}

characters.save(partyId, character.updatePoints(points.copy(wounds = wounds.current)))
}
is CombatantItem.Npc -> {
val npc = npcs.get(combatant.npcId)

if (npc.wounds == wounds) {
return
}
val character = characters.get(combatant.characterId)
val points = character.points

npcs.save(combatant.npcId.encounterId, npc.updateCurrentWounds(wounds.current))
}
if (points.wounds == wounds.current) {
return
}

characters.save(partyId, character.updatePoints(points.copy(wounds = wounds.current)))
}

suspend fun updateConditions(combatant: CombatantItem, conditions: CurrentConditions) {
Expand All @@ -328,20 +253,13 @@ class CombatScreenModel(
return
}

when (combatant) {
is CombatantItem.Character -> {
val character = characters.get(combatant.characterId)

if (character.conditions == conditions) {
return
}
val character = characters.get(combatant.characterId)

characters.save(partyId, character.updateConditions(conditions))
}
is CombatantItem.Npc -> {
// NPC do not have conditions, so this must have been handled as combatant specific
}
if (character.conditions == conditions) {
return
}

characters.save(partyId, character.updateConditions(conditions))
}

suspend fun updateAdvantage(combatant: Combatant, advantage: Advantage) {
Expand Down
Loading
Loading