Skip to content

Commit

Permalink
Merge pull request #5 from fmasa/talent-effects
Browse files Browse the repository at this point in the history
Talent Effects
  • Loading branch information
fmasa authored Aug 30, 2022
2 parents 86bf836 + 544e91a commit cc1fed7
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import cz.frantisekmasa.wfrp_master.common.character.CharacterPickerScreenModel
import cz.frantisekmasa.wfrp_master.common.character.CharacterScreenModel
import cz.frantisekmasa.wfrp_master.common.character.characteristics.CharacteristicsScreenModel
import cz.frantisekmasa.wfrp_master.common.character.combat.CharacterCombatScreenModel
import cz.frantisekmasa.wfrp_master.common.character.effects.TraitEffectFactory
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectFactory
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectManager
import cz.frantisekmasa.wfrp_master.common.character.religion.blessings.BlessingsScreenModel
import cz.frantisekmasa.wfrp_master.common.character.religion.miracles.MiraclesScreenModel
import cz.frantisekmasa.wfrp_master.common.character.skills.SkillsScreenModel
Expand Down Expand Up @@ -174,7 +175,7 @@ val appModule = DI.Module("Common") {
SpellsScreenModel(characterId, instance(), instance())
}
bindFactory { characterId: CharacterId ->
TalentsScreenModel(characterId, instance(), instance())
TalentsScreenModel(characterId, instance(), instance(), instance())
}
bindFactory { characterId: CharacterId ->
MiraclesScreenModel(characterId, instance(), instance())
Expand All @@ -183,9 +184,10 @@ val appModule = DI.Module("Common") {
BlessingsScreenModel(characterId, instance(), instance())
}

bindSingleton { TraitEffectFactory() }
bindSingleton { EffectFactory() }
bindSingleton { EffectManager(instance(), instance(), instance(), instance(), instance()) }
bindFactory { characterId: CharacterId ->
TraitsScreenModel(characterId, instance(), instance(), instance(), instance(), instance())
TraitsScreenModel(characterId, instance(), instance(), instance())
}
bindProvider { InvitationScreenModel(instance(), instance(), instance()) }
bindProvider { PartyListScreenModel(instance()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,91 @@ open class CharacteristicChange(

return null
}

fun fromTalentNameOrNull(name: String): CharacteristicChange? {
val cleanName = name.lowercase()

if (cleanName == "savvy") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
intelligence = 5,
),
)
}

if (cleanName == "suave") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
fellowship = 5,
),
)
}

if (cleanName == "marksman") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
ballisticSkill = 5,
),
)
}

if (cleanName == "very strong") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
strength = 5,
),
)
}

if (cleanName == "sharp") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
initiative = 5,
),
)
}

if (cleanName == "lightning reflexes") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
agility = 5,
),
)
}

if (cleanName == "coolheaded") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
willPower = 5,
),
)
}

if (cleanName == "very resilient") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
toughness = 5,
),
)
}

if (cleanName == "nimble fingered") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
dexterity = 5,
),
)
}

if (cleanName == "warrior born") {
return CharacteristicChange(
plus = Stats.ZERO.copy(
weaponSkill = 5,
),
)
}

return null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cz.frantisekmasa.wfrp_master.common.character.effects

class EffectFactory {
fun getEffects(item: EffectSource): List<CharacterEffect> {
return when (item) {
is EffectSource.Trait -> {
val name = item.trait.evaluatedName.trim()

listOfNotNull(
SizeChange.fromTraitNameOrNull(name),
CharacteristicChange.fromTraitNameOrNull(name),
SwarmWoundsModification.fromTraitNameOrNull(name),
)
}
is EffectSource.Talent -> {
val name = item.talent.name.trim()

listOfNotNull(
HardyWoundsModification.fromTalentOrNull(name, item.talent.taken.toUInt()),
CharacteristicChange.fromTalentNameOrNull(name),
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package cz.frantisekmasa.wfrp_master.common.character.effects

import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character
import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterRepository
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
import cz.frantisekmasa.wfrp_master.common.core.domain.talents.TalentRepository
import cz.frantisekmasa.wfrp_master.common.core.domain.traits.TraitRepository
import cz.frantisekmasa.wfrp_master.common.core.shared.IO
import cz.frantisekmasa.wfrp_master.common.firebase.firestore.Firestore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first

class EffectManager(
private val characters: CharacterRepository,
private val traits: TraitRepository,
private val talents: TalentRepository,
private val effectFactory: EffectFactory,
private val firestore: Firestore,
) {

suspend fun saveEffectSource(
characterId: CharacterId,
source: EffectSource,
): Unit = coroutineScope {
val characterDeferred = async(Dispatchers.IO) { characters.get(characterId) }

val effectSources = effectSources(characterId)
val previousSourceVersion = effectSources.firstOrNull { it.id == source.id }
val otherEffects = effectSources
.filter { it.id != source.id }
.flatMap { effectFactory.getEffects(it) }

val character = characterDeferred.await()
var updatedCharacter = if (previousSourceVersion != null)
character.revert(effectFactory.getEffects(previousSourceVersion), otherEffects)
else character

val newEffects = effectFactory.getEffects(source)
updatedCharacter = updatedCharacter.apply(newEffects, otherEffects)

firestore.runTransaction { transaction ->
if (updatedCharacter != character) {
characters.save(transaction, characterId.partyId, updatedCharacter)
}

when (source) {
is EffectSource.Trait -> {
traits.save(transaction, characterId, source.trait)
}
is EffectSource.Talent -> {
talents.save(transaction, characterId, source.talent)
}
}
}
}

suspend fun removeEffectSource(
characterId: CharacterId,
source: EffectSource,
): Unit = coroutineScope {
val characterDeferred = async(Dispatchers.IO) { characters.get(characterId) }

val effectSources = effectSources(characterId)
val otherEffects = effectSources
.filter { it.id != source.id }
.flatMap { effectFactory.getEffects(it) }

val character = characterDeferred.await()
val updatedCharacter = character.revert(effectFactory.getEffects(source), otherEffects)

firestore.runTransaction { transaction ->
if (updatedCharacter != character) {
characters.save(transaction, characterId.partyId, updatedCharacter)
}

when (source) {
is EffectSource.Trait -> {
traits.remove(transaction, characterId, source.trait.id)
}
is EffectSource.Talent -> {
talents.remove(transaction, characterId, source.talent.id)
}
}
}
}

private suspend fun effectSources(characterId: CharacterId): List<EffectSource> = coroutineScope {
val traits = async(Dispatchers.IO) { traits.findAllForCharacter(characterId).first() }
val talents = async(Dispatchers.IO) { talents.findAllForCharacter(characterId).first() }

traits.await().map { EffectSource.Trait(it) } +
talents.await().map { EffectSource.Talent(it) }
}

private fun Character.apply(
effects: List<CharacterEffect>,
otherEffects: List<CharacterEffect>,
): Character {
return effects.fold(this) { character, effect -> effect.apply(character, otherEffects) }
}

private fun Character.revert(
effects: List<CharacterEffect>,
otherEffects: List<CharacterEffect>,
): Character {
return effects.fold(this) { character, effect -> effect.revert(character, otherEffects) }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cz.frantisekmasa.wfrp_master.common.character.effects

import com.benasher44.uuid.Uuid
import cz.frantisekmasa.wfrp_master.common.core.domain.talents.Talent as CharacterTalent
import cz.frantisekmasa.wfrp_master.common.core.domain.traits.Trait as CharacterTrait

sealed interface EffectSource {
val id: Uuid

data class Trait(val trait: CharacterTrait) : EffectSource {
override val id: Uuid get() = trait.id
}

data class Talent(val talent: CharacterTalent) : EffectSource {
override val id: Uuid get() = talent.id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cz.frantisekmasa.wfrp_master.common.character.effects

import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character

class HardyWoundsModification(private val timesTaken: UInt): CharacterEffect {
override fun apply(character: Character, otherEffects: List<CharacterEffect>): Character {
val modifiers = character.woundsModifiers

return character.modifyWounds(
modifiers.copy(
extraToughnessBonusMultiplier = modifiers.extraToughnessBonusMultiplier + timesTaken,
)
)
}

override fun revert(character: Character, otherEffects: List<CharacterEffect>): Character {
val modifiers = character.woundsModifiers

return character.modifyWounds(
modifiers.copy(
extraToughnessBonusMultiplier = (modifiers.extraToughnessBonusMultiplier - timesTaken)
.coerceAtLeast(0.toUInt()),
)
)
}

companion object {
fun fromTalentOrNull(name: String, timesTaken: UInt): HardyWoundsModification? {
if (name.equals("hardy", ignoreCase = true)) {
return HardyWoundsModification(timesTaken)
}

return null
}
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cz.frantisekmasa.wfrp_master.common.character.talents

import cafe.adriel.voyager.core.model.coroutineScope
import com.benasher44.uuid.Uuid
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectManager
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectSource
import cz.frantisekmasa.wfrp_master.common.core.CharacterItemScreenModel
import cz.frantisekmasa.wfrp_master.common.core.domain.compendium.Compendium
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
Expand All @@ -16,12 +18,15 @@ class TalentsScreenModel(
private val characterId: CharacterId,
private val talentRepository: TalentRepository,
private val compendium: Compendium<CompendiumTalent>,
private val effectManager: EffectManager,
) : CharacterItemScreenModel<Talent, CompendiumTalent>(characterId, talentRepository, compendium) {

suspend fun saveTalent(talent: Talent) = talentRepository.save(characterId, talent)
suspend fun saveTalent(talent: Talent) {
effectManager.saveEffectSource(characterId, EffectSource.Talent(talent))
}

fun removeTalent(talent: Talent) = coroutineScope.launch(Dispatchers.IO) {
talentRepository.remove(characterId, talent.id)
effectManager.removeEffectSource(characterId, EffectSource.Talent(talent))
}

suspend fun saveCompendiumTalent(talentId: Uuid, compendiumTalentId: Uuid, timesTaken: Int) {
Expand All @@ -30,8 +35,7 @@ class TalentsScreenModel(
itemId = compendiumTalentId,
)

talentRepository.save(
characterId,
saveTalent(
Talent(
id = talentId,
compendiumId = compendiumTalent.id,
Expand Down
Loading

0 comments on commit cc1fed7

Please sign in to comment.