Skip to content

Commit cc1fed7

Browse files
authored
Merge pull request #5 from fmasa/talent-effects
Talent Effects
2 parents 86bf836 + 544e91a commit cc1fed7

File tree

12 files changed

+306
-87
lines changed

12 files changed

+306
-87
lines changed

common/src/commonMain/kotlin/cz/frantisekmasa/wfrp_master/common/DependencyInjection.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import cz.frantisekmasa.wfrp_master.common.character.CharacterPickerScreenModel
55
import cz.frantisekmasa.wfrp_master.common.character.CharacterScreenModel
66
import cz.frantisekmasa.wfrp_master.common.character.characteristics.CharacteristicsScreenModel
77
import cz.frantisekmasa.wfrp_master.common.character.combat.CharacterCombatScreenModel
8-
import cz.frantisekmasa.wfrp_master.common.character.effects.TraitEffectFactory
8+
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectFactory
9+
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectManager
910
import cz.frantisekmasa.wfrp_master.common.character.religion.blessings.BlessingsScreenModel
1011
import cz.frantisekmasa.wfrp_master.common.character.religion.miracles.MiraclesScreenModel
1112
import cz.frantisekmasa.wfrp_master.common.character.skills.SkillsScreenModel
@@ -174,7 +175,7 @@ val appModule = DI.Module("Common") {
174175
SpellsScreenModel(characterId, instance(), instance())
175176
}
176177
bindFactory { characterId: CharacterId ->
177-
TalentsScreenModel(characterId, instance(), instance())
178+
TalentsScreenModel(characterId, instance(), instance(), instance())
178179
}
179180
bindFactory { characterId: CharacterId ->
180181
MiraclesScreenModel(characterId, instance(), instance())
@@ -183,9 +184,10 @@ val appModule = DI.Module("Common") {
183184
BlessingsScreenModel(characterId, instance(), instance())
184185
}
185186

186-
bindSingleton { TraitEffectFactory() }
187+
bindSingleton { EffectFactory() }
188+
bindSingleton { EffectManager(instance(), instance(), instance(), instance(), instance()) }
187189
bindFactory { characterId: CharacterId ->
188-
TraitsScreenModel(characterId, instance(), instance(), instance(), instance(), instance())
190+
TraitsScreenModel(characterId, instance(), instance(), instance())
189191
}
190192
bindProvider { InvitationScreenModel(instance(), instance(), instance()) }
191193
bindProvider { PartyListScreenModel(instance()) }

common/src/commonMain/kotlin/cz/frantisekmasa/wfrp_master/common/character/effects/CharacteristicChange.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,91 @@ open class CharacteristicChange(
107107

108108
return null
109109
}
110+
111+
fun fromTalentNameOrNull(name: String): CharacteristicChange? {
112+
val cleanName = name.lowercase()
113+
114+
if (cleanName == "savvy") {
115+
return CharacteristicChange(
116+
plus = Stats.ZERO.copy(
117+
intelligence = 5,
118+
),
119+
)
120+
}
121+
122+
if (cleanName == "suave") {
123+
return CharacteristicChange(
124+
plus = Stats.ZERO.copy(
125+
fellowship = 5,
126+
),
127+
)
128+
}
129+
130+
if (cleanName == "marksman") {
131+
return CharacteristicChange(
132+
plus = Stats.ZERO.copy(
133+
ballisticSkill = 5,
134+
),
135+
)
136+
}
137+
138+
if (cleanName == "very strong") {
139+
return CharacteristicChange(
140+
plus = Stats.ZERO.copy(
141+
strength = 5,
142+
),
143+
)
144+
}
145+
146+
if (cleanName == "sharp") {
147+
return CharacteristicChange(
148+
plus = Stats.ZERO.copy(
149+
initiative = 5,
150+
),
151+
)
152+
}
153+
154+
if (cleanName == "lightning reflexes") {
155+
return CharacteristicChange(
156+
plus = Stats.ZERO.copy(
157+
agility = 5,
158+
),
159+
)
160+
}
161+
162+
if (cleanName == "coolheaded") {
163+
return CharacteristicChange(
164+
plus = Stats.ZERO.copy(
165+
willPower = 5,
166+
),
167+
)
168+
}
169+
170+
if (cleanName == "very resilient") {
171+
return CharacteristicChange(
172+
plus = Stats.ZERO.copy(
173+
toughness = 5,
174+
),
175+
)
176+
}
177+
178+
if (cleanName == "nimble fingered") {
179+
return CharacteristicChange(
180+
plus = Stats.ZERO.copy(
181+
dexterity = 5,
182+
),
183+
)
184+
}
185+
186+
if (cleanName == "warrior born") {
187+
return CharacteristicChange(
188+
plus = Stats.ZERO.copy(
189+
weaponSkill = 5,
190+
),
191+
)
192+
}
193+
194+
return null
195+
}
110196
}
111197
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cz.frantisekmasa.wfrp_master.common.character.effects
2+
3+
class EffectFactory {
4+
fun getEffects(item: EffectSource): List<CharacterEffect> {
5+
return when (item) {
6+
is EffectSource.Trait -> {
7+
val name = item.trait.evaluatedName.trim()
8+
9+
listOfNotNull(
10+
SizeChange.fromTraitNameOrNull(name),
11+
CharacteristicChange.fromTraitNameOrNull(name),
12+
SwarmWoundsModification.fromTraitNameOrNull(name),
13+
)
14+
}
15+
is EffectSource.Talent -> {
16+
val name = item.talent.name.trim()
17+
18+
listOfNotNull(
19+
HardyWoundsModification.fromTalentOrNull(name, item.talent.taken.toUInt()),
20+
CharacteristicChange.fromTalentNameOrNull(name),
21+
)
22+
}
23+
}
24+
}
25+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package cz.frantisekmasa.wfrp_master.common.character.effects
2+
3+
import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character
4+
import cz.frantisekmasa.wfrp_master.common.core.domain.character.CharacterRepository
5+
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
6+
import cz.frantisekmasa.wfrp_master.common.core.domain.talents.TalentRepository
7+
import cz.frantisekmasa.wfrp_master.common.core.domain.traits.TraitRepository
8+
import cz.frantisekmasa.wfrp_master.common.core.shared.IO
9+
import cz.frantisekmasa.wfrp_master.common.firebase.firestore.Firestore
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.async
12+
import kotlinx.coroutines.coroutineScope
13+
import kotlinx.coroutines.flow.first
14+
15+
class EffectManager(
16+
private val characters: CharacterRepository,
17+
private val traits: TraitRepository,
18+
private val talents: TalentRepository,
19+
private val effectFactory: EffectFactory,
20+
private val firestore: Firestore,
21+
) {
22+
23+
suspend fun saveEffectSource(
24+
characterId: CharacterId,
25+
source: EffectSource,
26+
): Unit = coroutineScope {
27+
val characterDeferred = async(Dispatchers.IO) { characters.get(characterId) }
28+
29+
val effectSources = effectSources(characterId)
30+
val previousSourceVersion = effectSources.firstOrNull { it.id == source.id }
31+
val otherEffects = effectSources
32+
.filter { it.id != source.id }
33+
.flatMap { effectFactory.getEffects(it) }
34+
35+
val character = characterDeferred.await()
36+
var updatedCharacter = if (previousSourceVersion != null)
37+
character.revert(effectFactory.getEffects(previousSourceVersion), otherEffects)
38+
else character
39+
40+
val newEffects = effectFactory.getEffects(source)
41+
updatedCharacter = updatedCharacter.apply(newEffects, otherEffects)
42+
43+
firestore.runTransaction { transaction ->
44+
if (updatedCharacter != character) {
45+
characters.save(transaction, characterId.partyId, updatedCharacter)
46+
}
47+
48+
when (source) {
49+
is EffectSource.Trait -> {
50+
traits.save(transaction, characterId, source.trait)
51+
}
52+
is EffectSource.Talent -> {
53+
talents.save(transaction, characterId, source.talent)
54+
}
55+
}
56+
}
57+
}
58+
59+
suspend fun removeEffectSource(
60+
characterId: CharacterId,
61+
source: EffectSource,
62+
): Unit = coroutineScope {
63+
val characterDeferred = async(Dispatchers.IO) { characters.get(characterId) }
64+
65+
val effectSources = effectSources(characterId)
66+
val otherEffects = effectSources
67+
.filter { it.id != source.id }
68+
.flatMap { effectFactory.getEffects(it) }
69+
70+
val character = characterDeferred.await()
71+
val updatedCharacter = character.revert(effectFactory.getEffects(source), otherEffects)
72+
73+
firestore.runTransaction { transaction ->
74+
if (updatedCharacter != character) {
75+
characters.save(transaction, characterId.partyId, updatedCharacter)
76+
}
77+
78+
when (source) {
79+
is EffectSource.Trait -> {
80+
traits.remove(transaction, characterId, source.trait.id)
81+
}
82+
is EffectSource.Talent -> {
83+
talents.remove(transaction, characterId, source.talent.id)
84+
}
85+
}
86+
}
87+
}
88+
89+
private suspend fun effectSources(characterId: CharacterId): List<EffectSource> = coroutineScope {
90+
val traits = async(Dispatchers.IO) { traits.findAllForCharacter(characterId).first() }
91+
val talents = async(Dispatchers.IO) { talents.findAllForCharacter(characterId).first() }
92+
93+
traits.await().map { EffectSource.Trait(it) } +
94+
talents.await().map { EffectSource.Talent(it) }
95+
}
96+
97+
private fun Character.apply(
98+
effects: List<CharacterEffect>,
99+
otherEffects: List<CharacterEffect>,
100+
): Character {
101+
return effects.fold(this) { character, effect -> effect.apply(character, otherEffects) }
102+
}
103+
104+
private fun Character.revert(
105+
effects: List<CharacterEffect>,
106+
otherEffects: List<CharacterEffect>,
107+
): Character {
108+
return effects.fold(this) { character, effect -> effect.revert(character, otherEffects) }
109+
}
110+
111+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package cz.frantisekmasa.wfrp_master.common.character.effects
2+
3+
import com.benasher44.uuid.Uuid
4+
import cz.frantisekmasa.wfrp_master.common.core.domain.talents.Talent as CharacterTalent
5+
import cz.frantisekmasa.wfrp_master.common.core.domain.traits.Trait as CharacterTrait
6+
7+
sealed interface EffectSource {
8+
val id: Uuid
9+
10+
data class Trait(val trait: CharacterTrait) : EffectSource {
11+
override val id: Uuid get() = trait.id
12+
}
13+
14+
data class Talent(val talent: CharacterTalent) : EffectSource {
15+
override val id: Uuid get() = talent.id
16+
}
17+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cz.frantisekmasa.wfrp_master.common.character.effects
2+
3+
import cz.frantisekmasa.wfrp_master.common.core.domain.character.Character
4+
5+
class HardyWoundsModification(private val timesTaken: UInt): CharacterEffect {
6+
override fun apply(character: Character, otherEffects: List<CharacterEffect>): Character {
7+
val modifiers = character.woundsModifiers
8+
9+
return character.modifyWounds(
10+
modifiers.copy(
11+
extraToughnessBonusMultiplier = modifiers.extraToughnessBonusMultiplier + timesTaken,
12+
)
13+
)
14+
}
15+
16+
override fun revert(character: Character, otherEffects: List<CharacterEffect>): Character {
17+
val modifiers = character.woundsModifiers
18+
19+
return character.modifyWounds(
20+
modifiers.copy(
21+
extraToughnessBonusMultiplier = (modifiers.extraToughnessBonusMultiplier - timesTaken)
22+
.coerceAtLeast(0.toUInt()),
23+
)
24+
)
25+
}
26+
27+
companion object {
28+
fun fromTalentOrNull(name: String, timesTaken: UInt): HardyWoundsModification? {
29+
if (name.equals("hardy", ignoreCase = true)) {
30+
return HardyWoundsModification(timesTaken)
31+
}
32+
33+
return null
34+
}
35+
}
36+
37+
}

common/src/commonMain/kotlin/cz/frantisekmasa/wfrp_master/common/character/effects/TraitEffectFactory.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.

common/src/commonMain/kotlin/cz/frantisekmasa/wfrp_master/common/character/talents/TalentsScreenModel.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cz.frantisekmasa.wfrp_master.common.character.talents
22

33
import cafe.adriel.voyager.core.model.coroutineScope
44
import com.benasher44.uuid.Uuid
5+
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectManager
6+
import cz.frantisekmasa.wfrp_master.common.character.effects.EffectSource
57
import cz.frantisekmasa.wfrp_master.common.core.CharacterItemScreenModel
68
import cz.frantisekmasa.wfrp_master.common.core.domain.compendium.Compendium
79
import cz.frantisekmasa.wfrp_master.common.core.domain.identifiers.CharacterId
@@ -16,12 +18,15 @@ class TalentsScreenModel(
1618
private val characterId: CharacterId,
1719
private val talentRepository: TalentRepository,
1820
private val compendium: Compendium<CompendiumTalent>,
21+
private val effectManager: EffectManager,
1922
) : CharacterItemScreenModel<Talent, CompendiumTalent>(characterId, talentRepository, compendium) {
2023

21-
suspend fun saveTalent(talent: Talent) = talentRepository.save(characterId, talent)
24+
suspend fun saveTalent(talent: Talent) {
25+
effectManager.saveEffectSource(characterId, EffectSource.Talent(talent))
26+
}
2227

2328
fun removeTalent(talent: Talent) = coroutineScope.launch(Dispatchers.IO) {
24-
talentRepository.remove(characterId, talent.id)
29+
effectManager.removeEffectSource(characterId, EffectSource.Talent(talent))
2530
}
2631

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

33-
talentRepository.save(
34-
characterId,
38+
saveTalent(
3539
Talent(
3640
id = talentId,
3741
compendiumId = compendiumTalent.id,

0 commit comments

Comments
 (0)